0%

从0开始学java 03 :观察者模式

生活所迫呀。

观察者模式是一种行为型设计模式,它定义了一种对象之间一对多的依赖关系,使得当一个对象的状态发生改变时,所有依赖它的对象都会自动收到通知并更新。

为什么要用观察者模式?我们有下面几个原因:

  • 解耦通知逻辑:观察者模式的核心价值在于:发布者(被观察者)和订阅者(观察者)之间是解耦的。 发布者无需知道观察者是谁、做什么,只需要在状态发生变化时“通知大家”;观察者则只关心“是否订阅了该对象”,无需知道发布者的具体业务逻辑。
  • 自动通知,避免轮询:在没有观察者模式的情况下,我们可能需要让每个组件不断检查(轮询)状态的变化。这不仅效率低,还容易写出重复、混乱的代码。
  • 事件驱动架构的基础:现代前后端框架(如 React、Vue、Java Swing、Spring)中大量使用观察者模式或其变种(发布-订阅模式)来构建响应式系统。

示例

我们还是以实际的需求入手,举一个最经典的例子:

你正在开发一个气象站系统,它能实时监测温度、湿度、气压等数据。现在有多个布告板(显示面板)希望自动接收到最新的天气数据并显示,比如:

  • 当前天气布告板(CurrentConditionsDisplay)
  • 统计布告板(StatisticsDisplay)
  • 预报布告板(ForecastDisplay)
    一旦气象站更新数据,所有布告板都应立即更新并展示最新信息

首先,我们需要定义一个被观察者接口Subject

1
2
3
4
5
public interface Subject {  
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}

其规范了三项功能:

  • 注册观察者
  • 删除观察者
  • 通知所有的观察者

当然,我们也需要定义布告板观察者接口Observer

1
2
3
public interface Observer {  
public void update(float temp, float humidity, float pressure);
}

额外的,我们定义一个展示接口DisplayElement,当布告板需要显示时,调用方法display()

1
2
3
public interface DisplayElement {  
public void display();
}

定义完接口后,我们需要定义类来实现主题了。首先,定义一个实现被观察者接口Subject的类WeatherData

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
import java.util.ArrayList;  

public class WeatherData implements Subject{
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;

public WeatherData(){
observers = new ArrayList();
}

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

public void removeObserver(Observer o){
int i = observers.indexOf(o);
if (i > 0){
observers.remove(i);
}
}

public void notifyObservers(){
for (int i = 0; i<observers.size(); i++){
Observer observer = (Observer) observers.get(i);
observer.update(temperature, humidity, pressure);
}
}

// 一旦新数据来了,调用此方法
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}

随后,定义布告板(观察者)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;

public CurrentConditionsDisplay(Subject weatherData) {
weatherData.registerObserver(this); // 注册自己
}

public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}

public void display() {
System.out.println("当前天气:温度 = " + temperature + "°C,湿度 = " + humidity + "%");
}
}

测试一下这个程序:

1
2
3
4
5
6
7
8
9
10
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();

CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

weatherData.setMeasurements(30.4f, 65.2f, 1013.1f);
weatherData.setMeasurements(28.9f, 70.1f, 1012.5f);
}
}

java内置写法

Java 在早期(JDK 1.0)就内置了对观察者模式的支持:

  • java.util.Observable:被观察者(Subject)
  • java.util.Observer:观察者(Observer)

还是使用上面气象站的例子,首先,我们继承内置的类Observable定义一个被观察者(WeatherData)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Observable;

public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;

public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;

setChanged(); // 标记状态已更新
notifyObservers(); // 通知所有观察者(无参数版本)
}

public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getPressure() { return pressure; }
}

setChanged()Observable 提供的方法,必须调用这个方法,告诉系统“我准备好通知别人了”。如果不调用 setChanged(),后续的 notifyObservers() 什么都不会发生。
notifyObservers():遍历当前注册的所有观察者(通过 addObserver() 添加的),并依次调用它们的 update() 方法。默认参数为 null,也可以使用 notifyObservers(Object arg) 传递数据。

随后,继承内置类Observer定义一个观察者(布告板)

1
2
3
4
5
6
7
8
9
10
11
import java.util.Observer;
import java.util.Observable;

public class CurrentConditionsDisplay implements Observer {
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
WeatherData wd = (WeatherData) o;
System.out.println("当前天气:温度 = " + wd.getTemperature() + "°C,湿度 = " + wd.getHumidity() + "%");
}
}
}

update(Observable o, Object arg): 当被观察者调用 notifyObservers() 时,每个注册的观察者的 update() 方法都会被执行。参数 o 是被观察者对象本身(即 WeatherData)。参数 arg 是通过 notifyObservers(arg) 传递的附加数据(如果调用的是 notifyObservers(),则为 null)。

注册观察者、更新数据、触发通知:

1
2
3
4
5
6
7
8
9
10
11
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(); // 创建被观察者
CurrentConditionsDisplay display = new CurrentConditionsDisplay(); // 创建观察者

weatherData.addObserver(display); // 注册观察者到被观察者内部列表

// 模拟一次天气数据更新
weatherData.setMeasurements(25.6f, 65.0f, 1012.0f);
}
}

虽然 Java 提供了这个内置机制,但它有一些明显缺陷,因此在 JDK 9 后已标记为过时(Deprecated)。 缺点如下:

  • Observable 是一个类,不是接口,限制了继承(Java 不支持多继承)
  • 内部方法如 setChanged() 并非自动调用,容易遗漏
  • update() 方法传参设计不灵活
  • 不支持泛型

因此,实际上用我们一开始写的方式自定义 SubjectObserver 接口是最清晰和通用的方式。哈哈。
当我们在大型项目中,面临越来越复杂的模块通信、异步响应、数据流处理需求时,传统的观察者模式(无论是自定义接口还是 Java 原生的 Observer/Observable)都可能显得不够灵活、不够强大,可能需要涉及到事件驱动架构(EDA)或响应式编程,在后面的章节中,我们会讲到。