观察者模式

气象监测应用

假设我们有来自气象站的数据,我们需要设计一个程序将这些气象数据显示在3个不同的公告板上,当气象站的数据更新时,公告板上的数据也要随着更新。我们应该怎样设计这个类呢?

我们需要一个measurementsChanged方法,当气象站的数据变化时,能够调用这个方法,通知系统数据发生变更,然后更新公告板。我们可以有如下的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
class WeatherData {
...
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();

currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
...
}

这个设计有什么问题呢?

首先没有遵守之前的原则,它把变化的部分和不变化的部分放在了一起,其次上面是针对实现编程,而不是针对接口编程。
同时当我们要添加数据或者增加公告板时,就要对这个进行改动,这不符合对扩展开放,对修改关闭的原则。因此这种的做法是不合适的。

因此我们引入观察者模式,这个模式实际就是报纸订阅的模式,也就是说只有当我们订阅了消息,数据变化时,才会通知订阅者。

观察者模式

定义了对象之间的一对多依赖,这样一来,当一个对象的状态发生变化时,它的所有的依赖者都会收到通知并自动更新

根据针对接口编程的知道思想,我们首先定义我们的Subject以及Observer接口

1
2
3
4
5
6
7
8
9
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}

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

订阅接口主要定义了三个方法,一个是注册,一个是解注册,一个是通知。
而Observer接口则只有一个update方法,用于更新数据
然后我们在定义一个display接口,用于展示数据

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

然后我们需要实现一个weatherData类,这个类要实现subject接口,因为这个类要实现数据变更通知的功能

首先定义了4个成员:
private ArrayList observers 用于表示我们有多少个需要通知的对象
另外的3个是我们WeatherData本身就应该有的业务属性数据

我们要实现接口的三个函数,分别是订阅,通知以及解订阅。而通知是在数据变更以后才能进行的。

而数据变动的measurementChanged函数在什么时候调用呢?就是在我们实际更新数据的时候。

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
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 notifyObserver() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer) observers.get(i);
observer.update(temperature, humidity, pressure);
}
}

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

public void measurementChanged() {
notifyObserver();
}

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

定义好了subject,下步就需要设置一个observer,observer实际上就是我们的公告板,它需要实现Observer接口以及Displayment接口。在这个CurrentConditionDisplay类中我们定义了它的私有成员,这里面包含了它关心的业务数据temperature以及humidity。然后还定义了一个weatherData,这个是为了调用注册方法registerObserver的,这样我们就能够把订阅者注册到Subject里面了。

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

public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = 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("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}

当我们需要更多的公告板时,可以用相同的方法创建相应的类,它们都需要实现Observer接口以及Displayment接口。

测试代码如下:

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

CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);

weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 31.4f);
weatherData.setMeasurements(78, 90, 32.4f);
}
}

上面的代码的类图如下所示:
weatherDataDP

代码归档位置:

https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/observerPattern

java内置库实现

上面是我们根据观察者模式的原理的实现代码,在java库中已经有了内置的观察者模式的实现,我们可以直接使用。具体可以参加代码示例:
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/subjectDemo

显示 Gitment 评论