Design Patterns: Observer 观察者模式

观察者模式

行为型模式

在观察者模式中主要有两大类对象:

一个是被观察的对象,叫做主题SubjectSubject具有数据,需要更新并提供给另一大类对象。

这另一大类对象就是观察者ObserverObserver不断获取Subject的数据更新,而且主要是处于一种被动的状态,也就是等待Subject主动推送push,少数时候需要自己向Subject拉取pull

观察者和被观察者之间是松耦合的,因为被观察者并不需要知道具体是谁正在观察它,它只需要将观察者保存在列表里,然后统一向列表里的每个观察者进行推送即可。如果是不需要推送了的观察者,就直接从列表里删掉,新增观察者就添加到列表里。

一个简单的例子

已知的类和接口

  1. WeatherData类有三个gettter方法可以用来获取湿度、温度和气压:getTemperaturegetHumiditygetPressure,三个方法的具体实现未知。当数据发生变化时,应调用measurementsChanged方法。
  2. 三个用来显示天气数据的布告板类:CurrentConditionsDisplayStatisticsDisplayForecastDisplay。它们都有一个update方法来接收数据更新。

需求

  1. 三个布告板都能及时收到数据更新,也就是要在measurementsChanged方法中调用这三个布告板的update方法。
  2. 以后可能会加入新种类的布告板或者去掉旧的布告板,所以实现中要考虑可扩展性。

最直观的实现

按照需求1,最直观的实现方法自然是先在measurementsChanged中获取数据,然后直接对每个布告板调用update

1
2
3
4
5
6
7
8
9
10
11
12
private CurrentConditionsDisplay currentConditionsDisplay;
private StatisticsDisplay statisticsDisplay;
private ForecastDisplay forecastDisplay;

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);
}

显然,这样的做法是紧耦合的。因为不仅需要在WeatherData类中分别保存每个布告板的引用,而且在数据更新的时候还要分别对每个布告板调用update

现在只有三个布告板,代码量比较少,但是如果布告板比较多,代码量就会不断增加。而且还不符合需求2,可扩展性很差(只能通过修改源代码来进行增删)。

使用观察者模式

首先需要实现三个接口:Subject, Observer, DisplayElement,分别对应了被观察者、观察者和布告板。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Observer {
public void update(float temp, float humidity, float pressure);
}

public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}

public interface DisplayElement {
public void display();
}

然后将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
39
40
41
42
43
import java.util.ArrayList;

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

public WeatherData() {
observers = new ArrayList<Observer>();
}

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

public void removeObserver(Observer o) {
observers.remove(o);
}

public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature, humidity, pressure);
}
}

public void measurementsChanged(){
notifyObservers();
}

/**
* 手动更新数据
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}

将布告板实现为观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CurrentConditionsDisplay implements Observer, DisplayElement{
private float temperature;
private float humidity;
private Subject weatherData; // 保存引用,方便取消注册

public CurrentConditionsDisplay(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 condition: " + temperature + "F degrees, humidity: " + humidity + "%");
}
}

Java内置的支持

java.util中包含了Observable类和Observer接口,分别对应被观察者和观察者。与上面自行实现的不同之处在于:

  1. Observable调用setChanged之后调用notifyObservers才会真的去通知观察者。
  2. Observerupdate方法接收第一个参数是Observable,方便判断是哪个被观察者更新。
  3. Observable是一个类而不是接口,所以只能继承,不太符合面向接口编程的原则。

注意事项

  1. 当存在链式触发时,要小心可能出现循环引用。
  2. 上面的接口也可以定义成类
  3. 观察者可以实现为在运行时自主注册,而不一定要在构造方法里注册。