设计模式之观察者模式

设计模式之观察者模式

从一个气象监测应用说起:
我们想做一个气象应用,有一个WeatherData对象,该对象由气象站的各种感应装置提供温度,湿度,气压数据 来初始化。同时我们还要设立3个布告板,来根据天气数据 显示不同的内容。分别是:目前状况,气象统计,简单的预报。 同时我们还希望可以扩展该接口,即第三方可以根据数据来实现自己的布告板。

背景已经交代清楚了,这其实是一个一对多的关系,即多个布告板全都依赖WeatherData对象,根据WeatherData对象做出自己的改变。下面是一个错误的示范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class WeatherData{
float temp;
float humidity;
float pressure;
WeatherData(float temp,float humidity,float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
}
public void measurementsChanged(){
temp = getTemp();
humidity = getHumidity();
pressure = getPressure();
currentDonditionsDisplay.update(temp,humidity,pressure);
staticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
}
}

虽然它能够满足大部分条件,但是它不具备扩展性,每次添加新的布告板时,我们都需要修改代码,我们是针对接口编程而不是针对具体实现编程,布告板没有实现一个共同的接口,而且我们无法在运行的时候动态的删除或添加布告板(只能在measurementsChanged()方法中删除对该布告板的更新。所以为了搞定这个问题,我们引出观察者模式:

认识观察者模式:

观察者模式和报纸和杂志的订阅很像,报社出版报纸,向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来,只要你是他们的订户,你就会一直收到新报纸。当你不想在看报纸时,取消订阅,他们便不会再送新报纸给你。只要报社还在运营,就会一直有人向他们订阅报纸或取消订阅报纸。

出版者 + 订阅者 = 观察者模式

我们可以将他们改下名称, 出版者改为“主题”,订阅者改为 “观察者”(《Head First 设计模式》里面是这样子的)。

观察者模式的定义:

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都将会收到通知并自动更新。

好了,我们可以根据这个模式来解决我们的实际问题了。
在我们的问题中,布告板会随着WeatherData的变化而得到更新,所以很明显WeatherData是主题,而布告板是观察者。实现观察者模式的方法不只一种,但是以包含Subjec与Observer接口的最为常见,之后会讲到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
import java.util.*;
interface Subject {
void registerObserver(Observer o);
void removerObserver(Observer o);
void notifyObservers();
}
interface Observer {
void update(float temp, float humidity, float pressure);
}
interface Displayment {// 展示接口
void display();
}
class WeatherData implements Subject {
private float temp;
private float humidity;
private float pressure;
private List<Observer> observers = new ArrayList<Observer>();
WeatherData(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
}
@Override
public void registerObserver(Observer o) {
// TODO 自动生成的方法存根
observers.add(o);
}
@Override
public void removerObserver(Observer o) {
// TODO 自动生成的方法存根
int i = observers.indexOf(o);
if (i >= 0)
observers.remove(i);
}
@Override
public void notifyObservers() {
// TODO 自动生成的方法存根
for (Observer o : observers)
o.update(temp, humidity, pressure);
}
public void measurementsChanged(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
class CurrentConditionDisplay implements Observer, Displayment {
private float temp;
private float humidity;
private float pressure;
private Subject subject;
public CurrentConditionDisplay(Subject subject) {
// TODO 自动生成的构造函数存根
this.subject = subject;
subject.registerObserver(this);
}
public CurrentConditionDisplay() {
// TODO 自动生成的构造函数存根
}
@Override
public void update(float temp, float humidity, float pressure) {
// TODO 自动生成的方法存根
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void display() {
// TODO 自动生成的方法存根
System.out.println("当前气温:" + temp + "当前湿度:" + humidity + "当前气压:"
+ pressure);
}
}
public class WeatherDataTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(27.2f, 30.0f, 110f);
weatherData.registerObserver(new CurrentConditionDisplay());
weatherData.measurementsChanged(24.0f, 22.2f, 23.2f);
}
}

此实现有缺点。一个观察者可以有多个主题,观察者可根据主题来实现不同的update(),例如我们可以将update()改成update(Subject subject,Object obj)后面的是Object是主题给观察者的数据对象。
其实在这里观察者得到数据的方法是由主题推送的,观察者无法通过自身得到更新的数据,我们可以在主题中实现观察者访问数据的接口

1
2
3
4
5
6
7
8
9
10
//在主题中提供数据接口
public float getTemp(){
return temp;
}
public float getHumiditu(){
return humidity;
}
public float getPressure(){
return pressure;
}

java内置的观察者模式:

JAVA API有内置的观察者模式,在java.util包中,包含最基本的Observer接口和Observable类(主题的另一种说法,“可观察者”),里面的方法请参考JDK文档,在此我们就直接实现:

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
import java.util.*;
interface Displayment{//展示接口
void display();
}
class WeatherData extends Observable{
private float temp;
private float humidity;
private float pressure;
public WeatherData(){};
public void measurementsChanged(float temp, float humidity, float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
setChanged();//java自带的,让我们更新的时候有更多的弹性。
notifyObservers();
}
public float getTemp(){
return temp;
}
public float getHumidity(){
return humidity;
}
public float getPressure(){
return pressure;
}
}
class CurrentConditionDisplay implements Observer,Displayment{
private float temp;
private float humidity;
private float pressure;
private Observable observable;
public CurrentConditionDisplay(Observable observable) {
// TODO 自动生成的构造函数存根
this.observable = observable;
observable.addObserver(this);
}
public void display() {
// TODO 自动生成的方法存根
System.out.println("当前气温:"+temp+"当前湿度:"+humidity+"当前气压:"+pressure);
}
@Override
public void update(Observable o, Object data) {
// TODO 自动生成的方法存根
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData)o;
this.temp = weatherData.getTemp();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
}
public class WeatherDataTest{
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay display = new CurrentConditionDisplay(weatherData);
weatherData.measurementsChanged(11.0f, 1.2f, 11);
}
}

java.util.Observable的黑暗面:
可能你也注意到了,Observable是一个类,而不是一个接口,而且它自己也没有实现一个接口,因此在很多方面限制了它的使用与复用,不符合我们设计原则,针对接口编程而不是针对实现编程。
首先Observable是一个类,你必须设计一个类继承它,如果某类想同时具有另一个超类的行为,则会陷入两难,因为JAVAv不支持多继承。再者,Observable中的setChanged()方法是protected的,这就意味着除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象里来。