浅谈观察者模式

设计模式之观察者模式

概述

观察者模式平时经常能听到,也是JDK中使用最多的模式之一,它可以帮你的对象知悉现况,不会错过该对象感兴趣的事。对象甚至在运行时可决定是否要继续被通知。

例子

首先举一个应用场景的例子来引出为什么需要使用观察者模式。

场景:

团队刚刚和气象公司建立了项目合作关系,为气象公司建立下一代Internet气象观测站,该气象站必须建立在专利申请中的WeatherData对象上,由该对象负责追踪目前的天气情况(温度、湿度、气压)。我们的任务是建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherData对象获得最新的数据更新时,三种布告板必须实时更新。

而且这是一个可以扩展的气象站,合作方希望可以公布一组API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。

这里的WeatherData类中涉及到的方法如下所示:

1
2
3
4
5
6
7
8
9
10
class WeatherData{
getTemperature(); // 返回温度的方法。
getHumidity(); // 返回湿度的方法。
getPressure(); // 返回气压的方法。

/*
*一旦气象测试更新,此方法会被调用。
*/
measurementsChanged();
}

我们的主要目标是实现measurementsChanged()方法,好让它更新目前的状况、气象统计、天气预报的显示布告板。

认识观察者模式

首先看看报纸和杂志的订阅是怎么发生的:

  • 报社的业务就是出版报纸。
  • 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户就会一直收到新的报纸。
  • 当你不想看报纸的时候,取消订阅,他们就不会再送新报纸过来了。
  • 只要报社还在运营,就会一直有人向他们订阅报纸或者取消订阅报纸。

将这个例子映射到观察者模式中,出版者改称为“主题”,订阅者改称为“观察者”。主题对象的数据一旦发生改变,新的数据会以某种形式送到观察者手上。相反不是观察者的话,主题数据改变时就不会收到通知。

定义观察者模式

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

观察者模式提供了一种对象的设计,让主题和观察者之间松耦合

  • 关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
  • 任何时候都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以随时可以增加观察者。事实上在运行时可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样也可以在任何时候删除某些观察者。
  • 新类型观察者出现主题的代码不需要修改。假如有个新的具体类需要当观察者,不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,只会发送通知给所有实现了观察者接口的对象。
  • 可以独立地复用主题或观察者。如果在其他地方需要使用主题或者观察者,可以轻易地复用,因为二者并非紧耦合。
  • 改变主题或者观察者一方,并不会影响另一方。因为两者都是松耦合的,只要他们之间的接口仍被遵循,就可以自由地改变他们。

所以这里就要引出又一个设计原则:为了交互对象之间地松耦合设计而努力

实现气象站

根据上述讨论,需要开始对整个系统的实现了,Java中内置了为观察者模式的支持,但是这里暂时不使用而是自己来实现它。

首先建立接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Subject {
public void registerObserver(Observer o); // 该方法需要一个观察者作为变量,该观察者用来注册
public void removeObserver(Observer o); // 同上,该观察者用来删除
public void notifyObservers(); // 当主题状态发生改变时,该方法被调用,通知所有观察者对象。
}

public interface Observer {
public void update(float temp, float humidity, float pressure); // 所有观察者必须实现该方法。
}

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
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float huminity;
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 measurementsChanged() { // 当观测值发生改变时,调用该方法通知观察者。
notifyObservers();
}

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 Observser, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;

public CurrentConditionsDisplay(Subject weatherData) { //构造器需要weatherData作为注册用,保存引用方便可能取消注册。
this.weatherData = weatherData;
weatherData.registerObserver(this);
}

public void update(float temperature, float humidity, float pressure) { // 保存参数,调用display()。
this.temperature = temperature;
this.humidity = humidity;
displsy();
}

public void display() {
System.out.print();
}
}

存在的问题

按照上述做法会有一个问题,主题老是向观察者推送每一条更新,但不是所有观察者都对所有更新感兴趣,所以还存在另外一种模式,即主题提供getter方法,让观察者主动向其索取数据。

Java也内置了观察者模式的支持,只需要实现或者继承Observable(类)、或者Observer(接口)就好了。接下来利用内置的支持重做气象站。

首先将WeatherData改成使用Observable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;

public WeatherData() { // 构造器不再需要为了记住观察者们而建立数据结构了。
}

public void measurementsChanged() {
setChanged(); // 调用该方法表示状态已改变。
notifyObservers(); // 不使用该方法传数据,说明使用get的形式。(因为里面调用update)
}

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


// 省略一系列getter方法。
}

不要依赖于观察者被通知的次序。因为Observable实现了他的notifyObservers()方法,导致通知者次序可能和自己实现的不一样。

java.util.Observable的黑暗面

这里JDK中的Observable是一个类,甚至没有实现一个接口。从之前说过的设计原则中得知这并不是一件好事情。

  • 首先,因为它是一个类,必须设计一个类来继承它。如果某一个类想同时具有Observable类和另一个超类的行为,就会陷入两难,因为Java不支持多重继承。限制了Observable的复用潜力。
  • 再者,因为没有Observable接口,所以无法建立自己的实现,和内置的Observer API搭配使用 ,也无法将这个实现用另一种方法实现:如Observable将关键方法保护起来,如setChanged()(定义成了protected)。意味着:除非你继承Observable,否则无法创建Observable实例并组合到自己的对象中来。这个设计违反了 “多用组合,少用继承”原则。

JDK中还有很多地方都是用了观察者模式~

0%