观察者模式定义了一系列对象之间的一对多关系。当一个对象改变状态,其它依赖者都会收到通知。
本文例子及思路来源于《Head First 设计模式》

场景

  我们接到一个来自气象局的需求:气象局需要我们构建一套系统,这系统有两个公告牌,分别用于显示当前的实时天气和未来几天的天气预报。当气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据必360桌面截图20171221224238.jpg-32.4kB须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的公告牌。

概况

  这套系统主要包括三个部分:气象站(获取天气数据的物理设备)、WeatherData(接收气象站数据,并当数据有更新时提示公告牌进行更新)、公告牌(用于展示天气数据)。

WeatherData知道如何跟气象站联系,以获得天气数据。当天气数据有更新时,WeatherData会更新两个公告牌用于展示新的天气数据。

错误示范

在未接触观察者模式前,我写的代码可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
public class WeatherData {
//实例变量声明
...

public void measurementsChanged(){
String weather = getWeather();
Double degree = getDegree();

currentConditionsDisplay.update(weather, degree);
forecastDisplay.update(weather, drgee);
}
}

这个代码是典型的针对具体实现编程,这回导致我们再添加或删除公告牌时必须修改程序。接下来我们来看看观察者模式如何实现这一功能。

观察者模式介绍

  观察者模式面向的需求是:A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的时候做出反应。例如报社与客户,客户向报社订阅了报纸。只要有新报纸出版,报社就会给你送来新报纸。而当客户取消订阅后,此项服务变会终止。当客户再次订阅后,则又会有新的报纸推送。
  观察者模式通常基于SubjectObserver接口来设计,下面是类图:
360桌面截图20171221230222.jpg-47.1kB

  Subject是主题接口,registerObverser使其他对象注册为此主题的观察者(订阅),removeObverser把某对象从观察者中剔除(取消订阅)。ConcerteSubject一个具体主题总是实现主题接口,除了订阅和取消订阅方法外,具体主题还实现了notifyObserver()方法,此方法用于在状态改变时更新所有观察者。
  所有观察者必须实现观察者接口Observer,这个接口只有一个update方法,当主题改变时它被调用。ConcerteObserver可以是实现此接口的任意类,观察者必须订阅具体主题,以便接收更新。

观察者模式的应用

  结合上面的类图,我们可以将观察者模式应用到WeatherData项目中来。设计类图如下:
360桌面截图20171221232453.jpg-79kB

代码实现如下:

主题接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 主题接口
*/
public interface Subject {

/**
* 新订阅者订阅主题
* @param observer 某一订阅者
*/
void registerObserver(Observer observer);

/**
* 某一订阅者取消订阅本主题
* @param observer
*/
void removeObserver(Observer observer);

/**
* 主题发生改变时用于提醒所有订阅者
*/
void notifyObservers();

}

具体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
44
45
46
47
48
49
public class WeatherData implements Subject {

private List<Observer> observers;//主题的订阅者
private String weather;
private Double drgee;

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

@Override
public void registerObserver(Observer observer) {
if (observer != null){
observers.add(observer);
}
}

@Override
public void removeObserver(Observer observer) {
Iterator<Observer> observerIterator = observers.iterator();
while (observerIterator.hasNext()){
Observer o = observerIterator.next();
if (o.equals(observer)){
observerIterator.remove();
}
}
}

/**
* 提醒订阅者们新的消息
*/
@Override
public void notifyObservers() {
Iterator<Observer> observerIterator = observers.iterator();
while (observerIterator.hasNext()){
Observer observer = observerIterator.next();
observer.update(weather, drgee);
}
}

/**
* 气象状态有更新时调用此方法通知订阅者
*/
public void measurementsChanges(String weather, Double degree){
this.weather = weather;
this.drgee = degree;
notifyObservers();
}
}

观察者接口

1
2
3
4
5
6
7
8
9
10
public interface Observer {

/**
* 观察者更新数据
* @param weather 天气
* @param degree 温度
*/
void update( String weather, Double degree);

}

具体观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DegreeDisplayElement implements Observer, DisplayElement {

private Double degree;
private Subject subject;

public DegreeDisplayElement(Subject subject) {
this.subject = subject;
subject.registerObserver(this);
}

@Override
public void diplay() {
System.out.println("温度公告牌显示当前温度为:" + this.degree);
}

@Override
public void update(LocalDate localDate, String weather, Double degree) {
System.out.println("温度公告牌更新");
this.degree = degree;
diplay();
}

}