意图
Observer是行为模式的一种,允许你定义对象间一对多的关系,以便一个对象状态改变后,它的依赖者可以被通知并可以自动更新。
问题
假设你有两个对象,Customer和Store。商店采购了一批新产品,一些客户对这些产品很感兴趣。
客户可能每天都来商店看下是否有感兴趣的产品售卖,但大多情况下的是无意义的,因为产品还在路上。
另一方面,商店可以给所有的客户发送上新的邮件。但是对那些不关心这类产品的用户来说是不必要的。
因此,我们遇到了一个冲突:是客户浪费时间来顶起检查还是商店浪费时间来通知到错误的客户。
解决
我们把有一些有趣状态的对象叫做Publisher。把另外那些想要追踪状态改变的对象叫做Subscriber。
Observer模式提供一个发布者,他有要追踪状态订阅者的列表。这个列表可以通过几个订阅方法来供订阅者修改。因此,在任何时候,每个订阅者都能够把自己从列表中添加或者移除。
发布者每次有新的事件发布,它会遍历它的订阅者列表并调用订阅者的通知方法。
发布者可以和任何订阅者协作,因为他们都遵循通用的接口。这个接口生命力一个带有一些参数的通知方法,这些参数提供给订阅者事件的细节。
如果应用有几个发布者类型,通用的发布者接口也需要提取出来。它由几个订阅方法组成。这个接口允许订阅者观察发布者的状态却不必和某个具体的实现类耦合。
类比
杂志订阅
一旦你订阅了杂志或者报纸,你不在需要去商店检查是否有新的杂志或者报纸发布。相反,发布者将会在期刊发布后直接发送到你的邮箱。
发布者维护了订阅者的信息,并且知道他们感兴趣的杂志。如果订阅者不想发布者再发送新的杂志给他们,他们可以随时取消对杂志的订阅。
结构
Publisher(发布者)发布其他对象感兴趣的事件。这些事件发生在发布者改变了他的状态或者它执行力一些行为时。发布者包含订阅的基本设施,允许新订阅者的加入和老订阅者的退出。
当一个新事件发生,发布者遍历订阅者列表并调用他们的通知方法,这些方法声明在Subscriber(订阅者)接口中。
Subscriber(订阅者)声明通知接口。大多情况下,它只有update方法。这个方法可能有几个参数让订阅者可以获取到事件详情。
具体的订阅者实现通知接口并且响应发布者发布的事件。
订阅者只需要一个简单的方法来争取的处理更新。基于这个原因,发布者通常把一些上下文数据当作这个方法的参数传递给订阅者。甚至可能是发布者自身的引用。
客户端分别创建发布者和订阅者对象,并且把订阅者注册到发布者。
有时候,订阅者可以直接访问发布者是很便利的。这个链接通常通过订阅者的构造方法建立。它允许订阅者在收到通知后直接从发布者抓取更新的状态。
伪代码
在这个例子中,Observer允许文本编辑器对象通知其他对象状态的变化。订阅者列表是动态编译的。对象可以在运行期间开始或者停止对更新的监听。
在这个实现中,编辑器自己维护订阅者列表而不是把它委托给特别的helper对象。这允许其他对象复用订阅基础结构。因此,Observer模式允许动态配置对象内发生的各种事件的处理者。
添加新的订阅者不需要改变已经存在的发布者类,只要他们之间通过通用接口协作。
1 | // Base publisher class. It should include the subscription |
适用性
当一个对象的状态发生变化其他对象也需要改变,但是这些对象事先不知道或者需要动态改变时。比如,你在开发一个专注于按钮的GUI框架。你想你的客户端能够在用户按下按钮时回调一些定制的代码。
Observer模式允许任何实现了订阅者接口的对象到发布者订阅事件通知。
一些对象需要观察其他对象,但只在限定的时间内或者特别的情况下。
Observer模式让发布者维护一个动态的订阅列表。所有的订阅者可以在运行时随时加入或者离开这个列表。
如何实现
区分核心(独立的)功能和可选(依赖的)功能。前者扮演发布者,后者扮演订阅者。
创建Subscriber接口。大多情况下,一个update方法足够。
创建Publisher接口并且定义开始和结束订阅操作。切记,发布者和订阅者通过通用接口协作。
你必须决定真实的订阅列表和订阅方法的实现放在哪里。通常,这些代码对所有类型的发布者而言都差不多。所以最恰当的地方是放在Publisher接口派生出的抽象基类中。但是如果你在已存在的类结构层次中集成Observer,那么创建一个小的helper类来维护制定发布者的订阅关系会比较方便。
创建具体的发布者类。在每次有重要事件发生时,他们都应当通知列表中所有的订阅者。
在具体的订阅者类中实现update方法。大多订阅者需要事件的上下文数据。可以通过update方法的参数传递给接受者。但是还有另外的方式。在收到通知后,接受者可以直接从发布者对象中抓取任何数据。在这种情况下,发布者必须把它自己当作参数传递给接受者。最不灵活的方法是通过订阅者构造方法持久的建立和发布者的关系。
客户端代码必须创建所有必须的订阅者并把它们注册到正确的发布者上。
优点
发布者不会和具体的订阅者类耦合。
你可以动态的订阅或者取消订阅。
-遵循开闭原则。
缺点
通知到订阅者的顺序是随机的。
和其他模式的关系
CoR、Command、Mediator和Observer处理各种方式的发送者和接受者:
CoR让请求沿着一个动态的潜在接受者链条传递直到某个接口者处理它。
Command建立了发送者和接受者的单项链接。
Mediator让发送者和接受者相互间简介关联。
Observer在同一时间把请求发送给所有关心的接收者,但是允许他们动态的订阅和取消订阅之后的请求。
Mediator和Observer模式的区别常常很模糊。在大多数情况下,模式间是竞争的,但有时他们可以合作。
Mediator调模式的主要目标是消除一组系统组件之间的相互依赖关系。所有组件都变成独立的对象。另一方面,Observer的主要目的是建立对象间动态的单向链接,其中一些随想作为其他对象的下属。
有一个非常受欢迎的Mediator实现依赖于Observer模式。中间人对象扮演发布者,所有的同事组件扮演订阅者。他们动态的订阅和取消订阅中间人的事件。这种实现方式看起来和两种模式都很像。
但是Mediator模式可以有不同的实现方式,比如所有的组件都永久连接到形同的中间人对象。这种实现看起来不像Observer,但仍是Mediator模式的一个例子。
现在想象另一个程序,其中所有的组件都是发布者,并允许彼此动态链接。这里没有一个中央中间人对象,而是一个分布式的观察员系统。