19、观察者模式实战:气象站数据更新系统(Subject推送数据给多个Observer)

观察者模式,说白了就是「一对多」的通知机制。一个对象状态变了,所有依赖它的对象都能自动收到通知。我最早接触这个模式,是在做一个物联网气象站项目的时候。当时被各种传感器数据同步搞得焦头烂额,后来用观察者模式重构,整个世界都清净了。

什么是观察者模式?

你想想看,气象站每天要采集温度、湿度、气压。这些数据变了,大屏要刷新、手机App要更新、数据库要存储。如果每个地方都去轮询气象站,那效率得多低?

观察者模式就是解决这个问题的。它有两个核心角色:

  • Subject(主题):也就是气象站本身,负责维护观察者列表,数据变了就通知大家
  • Observer(观察者):比如大屏显示、手机App、数据存储模块,它们订阅气象站的数据更新

嗯,这里要注意:Subject只管推送,不关心Observer怎么处理数据。这就是解耦的精髓。

我项目中的真实场景

我记得有一次,客户要求气象站数据要同时推送到三个终端:

  1. 大厅的LED大屏(显示实时数据)
  2. 后台数据库(做历史记录)
  3. 短信报警系统(温度超过阈值就发短信)

如果不用观察者模式,你就要在气象站代码里写死三个调用。哪天要加第四个终端呢?改代码呗。我经历过这种痛苦,所以后来坚决用观察者模式。

代码实战:气象站数据更新系统

我们先定义观察者接口。所有观察者都必须实现这个接口:

// 观察者接口
public interface Observer {
    void update(float temperature, float humidity, float pressure);
}

然后是Subject接口,定义注册、移除、通知的方法:

// 主题接口
public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

接下来是气象站的具体实现。它维护一个观察者列表,数据变化时遍历通知:

import java.util.ArrayList;
import java.util.List;

public class WeatherStation implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherStation() {
        observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    // 气象站数据更新时调用此方法
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();  // 数据变了,通知所有观察者
    }
}

现在我们来写几个具体的观察者。比如大屏显示:

public class DisplayScreen implements Observer {
    private String name;

    public DisplayScreen(String name) {
        this.name = name;
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        System.out.println(name + " 显示:温度 " + temperature 
            + "°C,湿度 " + humidity + "%,气压 " + pressure + " hPa");
    }
}

再比如数据库存储模块:

public class DatabaseLogger implements Observer {
    @Override
    public void update(float temperature, float humidity, float pressure) {
        // 实际项目中这里会写SQL插入数据库
        System.out.println("数据库记录:[" + System.currentTimeMillis() 
            + "] 温度=" + temperature + ",湿度=" + humidity + ",气压=" + pressure);
    }
}

还有短信报警系统:

public class SmsAlert implements Observer {
    private float threshold = 40.0f;  // 温度阈值

    @Override
    public void update(float temperature, float humidity, float pressure) {
        if (temperature > threshold) {
            System.out.println("【短信报警】温度 " + temperature 
                + "°C 超过阈值 " + threshold + "°C,请立即处理!");
        }
    }
}

运行起来看看效果

public class WeatherStationDemo {
    public static void main(String[] args) {
        WeatherStation station = new WeatherStation();

        // 注册观察者
        station.registerObserver(new DisplayScreen("大厅大屏"));
        station.registerObserver(new DatabaseLogger());
        station.registerObserver(new SmsAlert());

        // 模拟气象站数据更新
        System.out.println("=== 第一次数据更新 ===");
        station.setMeasurements(25.5f, 65.0f, 1013.2f);

        System.out.println("\n=== 第二次数据更新(高温) ===");
        station.setMeasurements(42.3f, 30.0f, 1008.5f);
    }
}

输出结果:

=== 第一次数据更新 ===
大厅大屏 显示:温度 25.5°C,湿度 65.0%,气压 1013.2 hPa
数据库记录:[1700000000000] 温度=25.5,湿度=65.0,气压=1013.2
=== 第二次数据更新(高温) ===
大厅大屏 显示:温度 42.3°C,湿度 30.0%,气压 1008.5 hPa
数据库记录:[1700000001000] 温度=42.3,湿度=30.0,气压=1008.5
【短信报警】温度 42.3°C 超过阈值 40.0°C,请立即处理!

核心要点:气象站(Subject)只负责推送数据,不关心谁在接收。观察者各自处理自己的逻辑,互不干扰。这就是观察者模式最迷人的地方——松耦合。

观察者模式的核心逻辑图

下面这张图展示了气象站数据更新的完整流程。我习惯用这种图来跟团队成员沟通设计思路:

气象站观察者模式数据流 气象站 (Subject) 维护观察者列表 观察者列表 大屏显示 实时展示温湿度气压 数据库存储 记录历史数据 短信报警 温度超阈值发短信 setMeasurements() 触发 数据更新时,气象站遍历观察者列表,逐个推送最新数据 观察者各自处理数据,互不依赖

避坑指南

我曾经踩过的坑:

  • 观察者处理太慢:如果某个观察者执行耗时操作(比如写数据库),会阻塞其他观察者的通知。我建议用异步队列来处理,或者把耗时操作放到独立线程。
  • 循环依赖:观察者里又去修改Subject的状态,导致无限循环。我习惯在代码里加一个「正在通知」的标志位来防止这种情况。
  • 忘记移除观察者:对象不用了还留在观察者列表里,会造成内存泄漏。我一般在观察者的析构方法里调用 removeObserver。

什么时候用观察者模式?

场景 推荐使用 原因
一个对象变化影响多个对象 ✅ 是 典型的一对多场景,观察者模式最擅长
需要解耦发布者和订阅者 ✅ 是 双方只依赖接口,不依赖具体实现
观察者数量动态变化 ✅ 是 运行时可以随时注册或移除观察者
只需要一对一通知 ❌ 否 用简单回调或事件监听就够了

我的个人习惯:在项目初期,如果预感到某个数据源会被多个模块使用,我直接上观察者模式。哪怕现在只有一个消费者,也先把接口写好。因为加一个观察者比改架构要轻松得多。

观察者模式其实无处不在。Java的Swing事件监听、Spring的事件机制、甚至前端框架的响应式数据绑定,底层都是这个思想。你想想看,是不是这个道理?

公众号:蓝海资料掘金营,微信deep3321