观察者模式

设计模式笔记:观察者模式 (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
// 痛点:WeatherData 知道所有具体的布告板类
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);
// ...
}
}

这种设计的痛点非常明显:

  1. 紧密耦合WeatherData(底层数据)与 CurrentConditionsDisplay(高层显示)紧密地耦合在一起。WeatherData 必须知道所有具体的显示类。
  2. 违反开闭原则:如果想增加一个新的布告板(比如 MobileAlertDisplay),就必须修改 WeatherData 类的代码,添加新的引用和调用逻辑。
  3. 扩展性差:无法在运行时动态地添加或删除布告板。

核心问题:如何建立一种机制,让数据提供方(WeatherData)和数据消费方(布告板)能够松散地连接在一起,使得提供方根本不需要知道消费方的具体身份,就能在状态变化时通知它们?

三、它是什么 (What - The Solution)

观察者模式通过引入“订阅”机制完美地解决了这个问题。它将系统分为两个角色:主(Subject)观察者(Observer)

核心思想和角色:

  1. **主体 (Subject) / 可观察者 (Observable)**:
    • 这是一个接口或抽象类,定义了被观察对象的核心功能。
    • 它内部维护了一个观察者列表List<Observer>)。
    • 它提供了三个关键方法:registerObserver()(注册/订阅)、removeObserver()(移除/取消订阅)、notifyObservers()(通知所有观察者)。
  2. **观察者 (Observer)**:
    • 这也是一个接口或抽象类,定义了观察者必须实现的方法。
    • 通常只有一个方法:update()。当主体的状态发生变化时,这个方法会被调用。
  3. **具体主体 (Concrete Subject)**:
    • 实现了 Subject 接口。
    • 它持有具体的状态。当它的状态发生变化时,它会调用 notifyObservers() 方法来通知所有已注册的观察者。
  4. **具体观察者 (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)

基本步骤(以气象站为例,采用拉模型):

  1. 创建 Observer 接口,包含 update() 方法。
  2. 创建 Subject 接口,包含 register/remove/notify 方法。
  3. 创建 ConcreteSubjectWeatherData,实现 Subject
    • 内部维护一个 List<Observer>
    • 实现 register/remove/notify 方法。notify 方法会遍历列表并调用每个 observer.update()
    • 提供获取状态的 getter 方法(如 getTemperature())。
    • 有一个 setMeasurements() 方法,当数据更新时,它会调用 notifyObservers()
  4. 创建 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;

// 1. Subject 接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}

// 2. Observer 接口
interface Observer {
void update();
}

// 3. ConcreteSubject
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();
}

// Getters for observers to pull data
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getPressure() { return pressure; }
}

// 4. ConcreteObserver
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;
// ... other state for statistics

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):

  1. 松散耦合:观察者模式的核心优点。主体和观察者之间是松散耦合的,它们只知道对方实现了某个接口,无需了解对方的具体实现。
  2. 支持广播通信:主体可以向任意数量的观察者发送通知,无需修改代码。
  3. 动态增删:可以在运行时动态地添加或移除观察者。
  4. 符合开闭原则:可以轻松地增加新的观察者,而无需修改主体的代码。

缺点 (Cons):

  1. 更新顺序问题:如果观察者之间的更新有先后顺序要求,此模式默认不支持,需要额外逻辑来保证。
  2. 级联更新可能导致复杂性:一个观察者在 update 时可能会触发其他对象的改变,导致一系列连锁反应,使系统行为难以追踪。
  3. 内存泄漏风险(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)**:
    • 整个响应式编程范式都是建立在观察者模式的扩展之上,用于处理异步事件流。