观察者模式
发表于更新于
OpenGL观察者模式
Rif设计模式笔记:观察者模式 (Observer Pattern)
一、一句话概括
定义了对象之间一种一对多的依赖关系,当一个对象(被观察者/主体)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
二、为什么需要它 (Why - The Pain Point)
想象一下一个气象站应用。我们有一个 WeatherData 对象,它负责从硬件传感器获取最新的气象数据(温度、湿度、气压)。同时,我们有多个布告板需要展示这些数据:
CurrentConditionsDisplay:显示当前的天气情况。
StatisticsDisplay:显示平均/最高/最低气温。
ForecastDisplay:根据气压变化预测天气。
糟糕的设计(紧密耦合):
WeatherData 对象直接持有所有布告板的引用,并在数据更新时,手动调用每个布告板的特定更新方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class WeatherData { private float temperature; private float humidity; private CurrentConditionsDisplay currentDisplay; private StatisticsDisplay statisticsDisplay;
public void measurementsChanged() { currentDisplay.update(temperature, humidity); statisticsDisplay.update(temperature, humidity); } }
|
这种设计的痛点非常明显:
- 紧密耦合:
WeatherData(底层数据)与 CurrentConditionsDisplay(高层显示)紧密地耦合在一起。WeatherData 必须知道所有具体的显示类。
- 违反开闭原则:如果想增加一个新的布告板(比如
MobileAlertDisplay),就必须修改 WeatherData 类的代码,添加新的引用和调用逻辑。
- 扩展性差:无法在运行时动态地添加或删除布告板。
核心问题:如何建立一种机制,让数据提供方(WeatherData)和数据消费方(布告板)能够松散地连接在一起,使得提供方根本不需要知道消费方的具体身份,就能在状态变化时通知它们?
三、它是什么 (What - The Solution)
观察者模式通过引入“订阅”机制完美地解决了这个问题。它将系统分为两个角色:主(Subject) 和观察者(Observer)。
核心思想和角色:
- **主体 (Subject) / 可观察者 (Observable)**:
- 这是一个接口或抽象类,定义了被观察对象的核心功能。
- 它内部维护了一个观察者列表(
List<Observer>)。
- 它提供了三个关键方法:
registerObserver()(注册/订阅)、removeObserver()(移除/取消订阅)、notifyObservers()(通知所有观察者)。
- **观察者 (Observer)**:
- 这也是一个接口或抽象类,定义了观察者必须实现的方法。
- 通常只有一个方法:
update()。当主体的状态发生变化时,这个方法会被调用。
- **具体主体 (Concrete Subject)**:
- 实现了
Subject 接口。
- 它持有具体的状态。当它的状态发生变化时,它会调用
notifyObservers() 方法来通知所有已注册的观察者。
- **具体观察者 (Concrete Observer)**:
- 实现了
Observer 接口。
- 每个具体观察者在被通知后(即
update() 方法被调用时),会执行自己的更新逻辑(比如从主体那里拉取新数据并刷新显示)。
数据传递方式:推模型 (Push) vs. 拉模型 (Pull)
- 推模型:主体在通知观察者时,通过
update 方法的参数,将所有状态数据推送给观察者。update(temp, humidity, pressure)。
- 拉模型(更常用、更灵活):主体只通知观察者“有变化了”(
update()),观察者在 update 方法内部,通过调用主体的 getter 方法,主动拉取自己需要的数据。这让观察者可以按需获取信息。
Java 中的历史支持:
Java 曾内置了 java.util.Observable 类和 java.util.Observer 接口。但从 Java 9 开始,它们已被废弃 (deprecated)。原因是 Observable 是一个类而非接口,限制了继承;且其实现并非线程安全。现在推荐手动实现该模式,或者使用 java.beans.PropertyChangeListener。
四、怎么做 (How - The Blueprint)
基本步骤(以气象站为例,采用拉模型):
- 创建
Observer 接口,包含 update() 方法。
- 创建
Subject 接口,包含 register/remove/notify 方法。
- 创建
ConcreteSubject 类 WeatherData,实现 Subject。
- 内部维护一个
List<Observer>。
- 实现
register/remove/notify 方法。notify 方法会遍历列表并调用每个 observer.update()。
- 提供获取状态的
getter 方法(如 getTemperature())。
- 有一个
setMeasurements() 方法,当数据更新时,它会调用 notifyObservers()。
- 创建
ConcreteObserver 类,如 CurrentConditionsDisplay。
- 实现
Observer 接口。
- 持有一个
Subject 的引用,并在构造时向 Subject 注册自己。
- 在
update() 方法中,调用 subject.getTemperature() 等方法获取数据,然后更新自己的显示。
Java 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| import java.util.ArrayList; import java.util.List;
interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); }
interface Observer { void update(); }
class WeatherData implements Subject { private List<Observer> observers; private float temperature; private float humidity; private float pressure;
public WeatherData() { this.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(); } }
public void measurementsChanged() { notifyObservers(); }
public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } }
class CurrentConditionsDisplay implements Observer { private WeatherData weatherData;
public CurrentConditionsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); }
@Override public void update() { float temp = weatherData.getTemperature(); float humidity = weatherData.getHumidity(); System.out.println("Current conditions: " + temp + "F degrees and " + humidity + "% humidity"); } }
class StatisticsDisplay implements Observer { private WeatherData weatherData; public StatisticsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); }
@Override public void update() { System.out.println("Statistics: Avg/Max/Min temperature = ... (based on new data)"); } }
public class ObserverPatternDemo { public static void main(String[] args) { WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
System.out.println("--- Weather changes ---"); weatherData.setMeasurements(80, 65, 30.4f); System.out.println("\n--- Weather changes again ---"); weatherData.setMeasurements(82, 70, 29.2f); } }
|
五、UML 类图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @startuml skinparam classAttributeIconSize 0 hide empty members
interface Subject { + void registerObserver(Observer o) + void removeObserver(Observer o) + void notifyObservers() }
interface Observer { + void update() }
class WeatherData implements Subject { - List<Observer> observers - float temperature + void registerObserver(Observer o) + void removeObserver(Observer o) + void notifyObservers() + void setMeasurements(...) + float getTemperature() }
class CurrentConditionsDisplay implements Observer { - WeatherData weatherData + void update() }
class StatisticsDisplay implements Observer { - WeatherData weatherData + void update() }
' 关系 ' Subject has many Observers WeatherData o-- "0..*" Observer
' Observers know their Subject to pull data CurrentConditionsDisplay --> WeatherData StatisticsDisplay --> WeatherData
class ObserverPatternDemo {} ObserverPatternDemo ..> WeatherData ObserverPatternDemo ..> CurrentConditionsDisplay @enduml
|
六、优缺点分析
优点 (Pros):
- 松散耦合:观察者模式的核心优点。主体和观察者之间是松散耦合的,它们只知道对方实现了某个接口,无需了解对方的具体实现。
- 支持广播通信:主体可以向任意数量的观察者发送通知,无需修改代码。
- 动态增删:可以在运行时动态地添加或移除观察者。
- 符合开闭原则:可以轻松地增加新的观察者,而无需修改主体的代码。
缺点 (Cons):
- 更新顺序问题:如果观察者之间的更新有先后顺序要求,此模式默认不支持,需要额外逻辑来保证。
- 级联更新可能导致复杂性:一个观察者在
update 时可能会触发其他对象的改变,导致一系列连锁反应,使系统行为难以追踪。
- 内存泄漏风险(Lapsed Listener Problem):如果一个观察者被注册后,在它不再需要时没有被正确地移除,它将无法被垃圾回收,同时也会持有对主体的引用,导致主体也无法被回收。
七、在 Java 中的应用
观察者模式是事件驱动编程的基石,在 Java 中无处不在。
- **GUI 事件处理 (AWT/Swing/JavaFX)**:
JButton 是主体,ActionListener 是观察者。你通过 button.addActionListener(myListener) 注册,当按钮被点击时,myListener.actionPerformed(event) 方法被调用。
- JavaBeans 组件:
java.beans.PropertyChangeListener 机制,允许 bean(主体)在属性改变时,通知已注册的监听器(观察者)。
- **消息队列 (JMS, RabbitMQ, Kafka)**:
- 发布-订阅模型是观察者模式在分布式系统中的宏观体现。生产者是主体,消费者是观察者。
- **Reactive Streams (RxJava, Project Reactor)**:
- 整个响应式编程范式都是建立在观察者模式的扩展之上,用于处理异步事件流。