0%

设计模式学习之-02ObserverPattern

1. 天气应用

系统中共有3中角色:

  1. 天气站: 获取真实天气数据的物理设备
  2. 天气数据对象: 从天气站获取数据并更新显示设备
  3. 显示设备: 向用户展示当前的天气

需求:

  1. WeatherData对象可以获取天气信息,信息发生变化时,将信息推送给显示设备
  2. 有三种显示设备:current conditions, weather stats, forecast

2. Observer Pattern

2.1. 订阅报纸或杂志是怎么工作的?

  1. 报纸出版商开始发布报纸
  2. 订阅者向出版商订阅报纸,每次发行了新的报纸,出版商会把新报纸寄给订阅者
  3. 如果你不想在看报纸,你可以取消订阅,出版商就会停止报纸的邮寄
  4. 只要出版商还存在,人们、旅馆等就可以不断地订阅或取消订阅出版物。

2.2. Publisher + Subscribers = Observer Pattern

以报纸订阅为例:

newspaper object表示一个报纸出版商(publisher)的对象,Joe,Jenny,Tom是三个用户对象,他们向newspaper object发起订阅(或称向newspaper注册),完成了订阅的动作之后他们就成为了订阅者,当newspaper发布新一期的报纸时,这三个用户将会收到更新。我们将发布者(publisher)称为subject,将订阅者(subscribers)称为Observers。

这时一个非订阅者Penny也想要获取报纸,Penny需要向出版商订阅

现在Penny就成为了一个Observer,报纸更新时,就会推送给Penny和其他的Observer

如果用户Tom不想再继续看报纸了,他就可以取消订阅,此时Tom就不再是一个Observer了

newspaper object就会把Tom移出推送列表,报纸更新时就不再会推送给Tom

2.3. 定义

The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state,all of its dependents are notified and updated automatically.

2.4. 观察者模式类图

2.5. 松耦合

当两个对象之间的关系是松耦合时,他们可以交互,但是他们只知道彼此很少的信息。

观察者模式提供了一种subject和observer对象间松耦合的设计模式。

  1. subject对于observer的了解仅仅是知道observer实现了一个特定的接口(Observer接口),subject不需要知道任何关于observer的其他信息
  2. 可以随时添加一个新的observer
  3. 增加新类型的observer时不需要修改subject
  4. 可以独立的重用subject和observer
  5. 改变subject或observer不会相互影响

松耦合设计允许我们构建灵活的OO系统,这个系统可以handle change,因为他们最小化了对象间的相互依赖

3. 天气应用的设计实现

4. push 与 pull

 在观察者模式中,常用的有推模型和拉模型两种方式。

  • 推模型

    主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。

  • 拉模型

    主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

4.1. push 模型

在push模型中Subject需要将所有更新的数据通过update函数通知观察者。

完整代码

一个具体的Subject定义如下:

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
public class WeatherData implements Subject {
private ArrayList<Observer> observers = new ArrayList<Observer>();
private float temperature;
private float humidity;
private float pressure;

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

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

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

一个具体的观察者定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CurrentCondition implements Observer, DisplayElement {
private float temperature;

@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
display();
}

@Override
public void display() {
System.out.println("CurrentCondition: " + "temperature is " + temperature + ".C");
}
}

从这个观察着的定义我们可以看出,在push模式下,即使一个观察者仅需要直到一个数值的变化,也会收到Subject对象推送的全部消息

4.2. pull 模型

在pull模型中。Subject仅需将this当做参数通知给Observers,observer可以根据需要来获取想要的信息。由于需要动态获取,Subject对象需要提供getter方法。

完整代码

一个具体的Subject定义如下:

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
public class WeatherData implements Subject {
private ArrayList<Observer> observers = new ArrayList<Observer>();
private float temperature;
private float humidity;
private float pressure;

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

@Override
public void notifyObservers() {
for (Observer o: observers) {
o.update(this);
}
}

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

public float getTemperature() {
return temperature;
}

public float getHumidity() {
return humidity;
}

public float getPressure() {
return pressure;
}
}

一个具体的观察者定义如下:

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

@Override
public void display() {
System.out.println("CurrentCondition: temperature is " + temperature + "humidity is " + humidity);
}

@Override
public void update(Subject subject) {
// 判断subject是否是WeatherData
if (subject instanceof WeatherData) {
this.temperature = ((WeatherData) subject).getTemperature();
this.humidity = ((WeatherData) subject).getHumidity();
display();
}
}
}

在这里CurrentCondition对象需要获取两个数值,因此调用了subject的getter方法。

5. 问题

在《header first design patterns》这本书中,是给实现Observer接口的类传入了实现Subject接口的类的参数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
public class CurrentConditionDisplay implements Observer, DisplayElement {
privite float temperature;
privite float humidity;
privite Subject weatherData;

// 是通过这种方式让weatherData对象添加observer
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
//...other methods
}

我认为这么写不太容易理解,但是搜了网上的一些帖子,绝大部分都是按照这种方式来写的。

我与书中的实现方式不同,我是将实现Observer接口的对象当做参数传给了Subject的对象,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class WeatherData implements Subject {
private ArrayList<Observer> observers = new ArrayList<Observer>();
private float temperature;
private float humidity;
private float pressure;

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
//...other methods
}

我想既然是WeatherData类维护observers的列表,为什么不把observer作为参数传给registerObserver函数呢?关于这一点,我还没有想明白,后期如果想明白了或者发现自己错了,再来修改。

———————————————分割线————————————————

关于上面说的问题,我想明白了,这里做一个解释:

通过向observer对象传递WeatherData对象,以达到observer对象自己管理注册和取消注册的动作,在我的代码的实现中,依赖于WeatherStation对于信息发布者和观察者的管理,需要WeatherStation告诉WeatherData对象要添加哪个对象作为观察者,或者告诉WeatherData需要将哪个对象从观察者列表移除。用书中的实现方式,当一个对象想要注册/取消注册时,仅需该对象调用自身的函数即可,比我的实现方式要好。