观察者模式
1)首先,我们要实现一个气温显示系统,有三个布告板需要相同的更新数据(温度、湿度、气压),由一个WeatherData类提供更新数据,要求每当数据更新时,要通知到三个布告板。最后还要有一个可扩展的第四个布告板,供其他开发人员建立定制的布告板。
一个错误的实现:

在这个错误的实现中:
- 我们是针对具体实现编程,而非针对接口
- 对于每个新的布告板,我们都得修改代码
- 我们无法再运行时动态地增加或删除布告板
- 我们尚未封装改变的部分
观察者模式与报纸的订阅非常相似:
- 报社的业务就是出版报纸
- 向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸
- 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来
- 只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消报纸
观察者模式=出版者+订阅者
只是在观察者模式中,出版者改称为“主题”(Subject),订阅者改称为“观察者”(Observer)



2)定义观察者模式
观察者模式定义了一系列对象之间的一对多关系。
当一个对象改变状态,其他依赖者都会受到通知。


实现观察者模式的方法不止一种,但是以包含Subject与Observer接口的类设计的做法最常见。
3)实现观察者模式
因为布告板的差异性,我们需要一个共同的接口,让布告板的类(可能不一样)都去实现相同的接口,好让WeatherData对象能够知道如何把观测值送给它们。
定义观察者模式类图:

设计气象站:


3)松耦合的威力
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
为什么呢?
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代旧的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。
有新类型的观察者出现时,主题的代码不需要修改。加入我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,也可以轻易地复用,因为二者并非紧耦合。
改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

4)实现气象站









5)观察者模式的“推(push)”和“拉(pull)”
到此为止,我们已经从无到有的完成了观察者模式。
仔细看我们实现的观察者模式会发现,所有的信息都是由主题“推(push)”给观察者的;
还有一种观察者模式是通过观察者从主题那边“拉(pull)”过来的,当然这需要主题设置相关的getter()方法。
6)Java内置的观察者模式
java.util包(package)内包含最基本的Observer接口与Observerable类,这和我们的Subject接口与Observer接口很相似。Observer接口与Observerable类使用上更方便,因为许多功能都已经事先准备好了,你甚至可以使用推(push)或拉(pull)的方式传送数据。
这是修改后的气象站OO设计:













7)小结
OO基础:
- 抽象
- 封装
- 多态
- 继承
OO原则:
- 封装变化
- 多用组合、少用继承
- 针对接口编程,不针对实现编程
- 为交互对象之间的松耦合设计而努力
OO模式:
- 策略模式——定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
- 观察者模式——在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新
要点:
- 观察者模式定义了对象之间一对多的关系
- 主题(也就是可观察者)用一个共同接口来更新观察者
- 观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者的接口
- 使用此模式时,你可以从被观察者处推(push)或拉(pull)数据(然而,推的方式被认为更“正确”)
- 有多个观察者时,不可依赖特定的通知次序
- Java有多种观察者模式的实现,包括通用的java.util.Observable
- 要注意java.util.Observable带来的一些问题
- 如果有必要的话,可以实现自己的Observable,这并不难
- Swing大量使用观察者模式,许多GUI框架也是如此
- 此模式也被应用在许多地方,比如:JavaBeans、BMI
参考书目:《Head First 设计模式》