19、观察者模式实战:气象站数据更新系统(Subject推送数据给多个Observer)
观察者模式,说白了就是「一对多」的通知机制。一个对象状态变了,所有依赖它的对象都能自动收到通知。我最早接触这个模式,是在做一个物联网气象站项目的时候。当时被各种传感器数据同步搞得焦头烂额,后来用观察者模式重构,整个世界都清净了。
什么是观察者模式?
你想想看,气象站每天要采集温度、湿度、气压。这些数据变了,大屏要刷新、手机App要更新、数据库要存储。如果每个地方都去轮询气象站,那效率得多低?
观察者模式就是解决这个问题的。它有两个核心角色:
- Subject(主题):也就是气象站本身,负责维护观察者列表,数据变了就通知大家
- Observer(观察者):比如大屏显示、手机App、数据存储模块,它们订阅气象站的数据更新
嗯,这里要注意:Subject只管推送,不关心Observer怎么处理数据。这就是解耦的精髓。
我项目中的真实场景
我记得有一次,客户要求气象站数据要同时推送到三个终端:
- 大厅的LED大屏(显示实时数据)
- 后台数据库(做历史记录)
- 短信报警系统(温度超过阈值就发短信)
如果不用观察者模式,你就要在气象站代码里写死三个调用。哪天要加第四个终端呢?改代码呗。我经历过这种痛苦,所以后来坚决用观察者模式。
代码实战:气象站数据更新系统
我们先定义观察者接口。所有观察者都必须实现这个接口:
// 观察者接口
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的状态,导致无限循环。我习惯在代码里加一个「正在通知」的标志位来防止这种情况。
- 忘记移除观察者:对象不用了还留在观察者列表里,会造成内存泄漏。我一般在观察者的析构方法里调用 removeObserver。
什么时候用观察者模式?
| 场景 | 推荐使用 | 原因 |
|---|---|---|
| 一个对象变化影响多个对象 | ✅ 是 | 典型的一对多场景,观察者模式最擅长 |
| 需要解耦发布者和订阅者 | ✅ 是 | 双方只依赖接口,不依赖具体实现 |
| 观察者数量动态变化 | ✅ 是 | 运行时可以随时注册或移除观察者 |
| 只需要一对一通知 | ❌ 否 | 用简单回调或事件监听就够了 |
我的个人习惯:在项目初期,如果预感到某个数据源会被多个模块使用,我直接上观察者模式。哪怕现在只有一个消费者,也先把接口写好。因为加一个观察者比改架构要轻松得多。
观察者模式其实无处不在。Java的Swing事件监听、Spring的事件机制、甚至前端框架的响应式数据绑定,底层都是这个思想。你想想看,是不是这个道理?