对象行为型模式——观察者模式
1. 意图
定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变是,所有依赖它的对象都得到通知并被自动更新。
2. 动机
将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间额的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。
在实际中,有很多这样的场景需要使用到,比如有一个新闻报社,还有一些订阅了这个报社的人们,当报社有新的信息的时候,就会自动的更新到这些人上面去,让这些人都能知道报社的最新情况。
Observer模式描述了如何建立这种关系。这一模式中的关键对象是目标和观察者。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变,所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标的状态同步。
这种交互也称为发布-订阅。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者。可以有任意数目的观察者订阅并接受通知。
3. 适用性
在以下任一种情况都可以使用观察者模式:
- 当一个抽象对象模型有两个方面,其中一个方面依赖于另一个方面。将这两者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
- 当一个对象必须通知其它对象,而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的。
4. 结构
5. 参与者
- Subject(目标)
—— 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
—— 提供注册和删除观察者对象的接口。 - Observer(观察者)
—— 为那些在目标发生改变时需获得通知的对象定义一个更新接口。 - ConcreteSubject(具体目标)
—— 将有关状态存入各ConcreteObserver对象。
—— 当它的状态发生改变时,向它的各个观察者发出通知。 - ConcreteObserver(具体观察者)
—— 维护一个指向ConcreteSubject对象的引用。
—— 存储有关状态,这些状态与目标的状态保持一致。
—— 实现Observer的更新接口以使自身状态与目标状态保持一致。
6. 协作
- 当ConcreteSubject发生任何改变的时候导致其观察者与其本身状态不一致的改变时,它将通知它的各个观察者。
- 在得到一个具体目标的改变通知后,oncreteObserver对象可向目标对象查新信息。ConcreteObserver使用这些信息以使他的状态与目标对象的状态一致。
7. 效果
- Observer模式允许你独立的改变目标和观察者。可以单独复用目标对象而无需同时复用其观察者,反之亦然。
- 目标和观察者间的抽象解耦。
- 支持广播通信。不想通常的请求,目标发送的通知不需指定它的接受者。通知被自动广播给所有已向该目标对象登记的有关对象。这给了你在任何时候增加或者删除观察者的自由。处理还是忽略一个通知取决于观察者。
- 意外的更新。因为一个观察者并不知道其它观察者的存在,它可能对改变目标的最终价值一无所知。
8. 实现
- 创建目标到其观察者间的映射。一个目标对象跟踪它应通知的观察者的最简单的方法是显示地在目标中保存它们的引用。然而,当目标很多而观察者较少时,这样存储可能代价太高。一个解决方法就是用时间换空间,同一个关联查找机制来维护目标到观察者的映射。
- 观察多个目标。在某一些情况下,一个观察者依赖于多个目标可能是有意义的。例如,一个读者可以订阅多个报刊。在这种情况下,必须扩展Update接口来使观察者知道是目标发送的通知。
- 谁触发更新。
一般有两种情况:
第一种由目标对象的状态在发生改变的时候就触发。这种方式的优点是客户不需要记住在目标状态上执行Notify。但是却存在一个缺点,比如一个报刊如果一次性更新几次报纸,那么每一个更新都会调用Nodify,这样的代价是巨大的。
第二种,由客户端负责在适当的时候调用Notify。这样就可以避免第一种情况的问题,但是却需要客户端注意调用Notify。 - 对与删除目标的悬挂引用。删除一个目标时应注意不要在其观察者中遗留对该目标的悬挂引用。
- 在发出通知前确保目标的状态自身是一致的。
- 避免特定于观察者的更新协议——推/拉模式。
推模式,目标向观察发送关于改变的详细信息,而不需管它们需要与否。
拉模式,目标除最小通知外什么也不送出,而在此之后由观察者显示地向目标访问细节。 - 显示地指定感兴趣的改变。有时候,用户可能感兴趣的东西挺不是一个报刊的全部,可能只对报刊的计算科学感兴趣,那么当报刊更新的时候,对于该用户只发送计算科学的内容。
- 封装复杂的更新予语义。当目标与观察者之间的依赖关系特别复杂时,我们需要一个维护这类关系的对象。我们称之为更改管理器。
更改管理器有三个功能:
a)它将一个目标映射到它的观察者并提供一个接口来维护。
它定义了一个特定更新策略。
根据一个目标的请求,他更新所有依赖于这个目标的观察者。
9. 代码示例
该代码的相关UML图如下:
其代码的Github地址如下:
https://github.com/VioletDream-SXZ/DesignPatterns/tree/master/BehaviorPattern
10. 参考文献
Erich Gamma,Richard Helm,Ralph Johnson,John Vissides.Design Patterns Elements of Reusable Object-Oriented Software[M].北京:机械工业出版社.2009:9.