1. 天气应用
系统中共有3中角色:
- 天气站: 获取真实天气数据的物理设备
- 天气数据对象: 从天气站获取数据并更新显示设备
- 显示设备: 向用户展示当前的天气
需求:
- WeatherData对象可以获取天气信息,信息发生变化时,将信息推送给显示设备
- 有三种显示设备:current conditions, weather stats, forecast
2. Observer Pattern
2.1. 订阅报纸或杂志是怎么工作的?
- 报纸出版商开始发布报纸
- 订阅者向出版商订阅报纸,每次发行了新的报纸,出版商会把新报纸寄给订阅者
- 如果你不想在看报纸,你可以取消订阅,出版商就会停止报纸的邮寄
- 只要出版商还存在,人们、旅馆等就可以不断地订阅或取消订阅出版物。
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对象间松耦合的设计模式。
- subject对于observer的了解仅仅是知道observer实现了一个特定的接口(Observer接口),subject不需要知道任何关于observer的其他信息
- 可以随时添加一个新的observer
- 增加新类型的observer时不需要修改subject
- 可以独立的重用subject和observer
- 改变subject或observer不会相互影响
松耦合设计允许我们构建灵活的OO系统,这个系统可以handle change,因为他们最小化了对象间的相互依赖
3. 天气应用的设计实现
4. push 与 pull
在观察者模式中,常用的有推模型和拉模型两种方式。
推模型
主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
拉模型
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
4.1. push 模型
在push模型中Subject需要将所有更新的数据通过update函数通知观察者。
一个具体的Subject定义如下:
1 | public class WeatherData implements Subject { |
一个具体的观察者定义如下:
1 | public class CurrentCondition implements Observer, DisplayElement { |
从这个观察着的定义我们可以看出,在push模式下,即使一个观察者仅需要直到一个数值的变化,也会收到Subject对象推送的全部消息
4.2. pull 模型
在pull模型中。Subject仅需将this当做参数通知给Observers,observer可以根据需要来获取想要的信息。由于需要动态获取,Subject对象需要提供getter方法。
一个具体的Subject定义如下:
1 | public class WeatherData implements Subject { |
一个具体的观察者定义如下:
1 | public class CurrentCondition implements Observer, DisplayElement { |
在这里CurrentCondition对象需要获取两个数值,因此调用了subject的getter方法。
5. 问题
在《header first design patterns》这本书中,是给实现Observer接口的类传入了实现Subject接口的类的参数,代码如下
1 | public class CurrentConditionDisplay implements Observer, DisplayElement { |
我认为这么写不太容易理解,但是搜了网上的一些帖子,绝大部分都是按照这种方式来写的。
我与书中的实现方式不同,我是将实现Observer接口的对象当做参数传给了Subject的对象,代码如下:
1 | public class WeatherData implements Subject { |
我想既然是WeatherData类维护observers的列表,为什么不把observer作为参数传给registerObserver函数呢?关于这一点,我还没有想明白,后期如果想明白了或者发现自己错了,再来修改。
———————————————分割线————————————————
关于上面说的问题,我想明白了,这里做一个解释:
通过向observer对象传递WeatherData对象,以达到observer对象自己管理注册和取消注册的动作,在我的代码的实现中,依赖于WeatherStation对于信息发布者和观察者的管理,需要WeatherStation告诉WeatherData对象要添加哪个对象作为观察者,或者告诉WeatherData需要将哪个对象从观察者列表移除。用书中的实现方式,当一个对象想要注册/取消注册时,仅需该对象调用自身的函数即可,比我的实现方式要好。