一、动机
观察者模式是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。
在软件构建过程中,我们需要为某些对象建立一种”通知依赖关系“——一个对象(目标对象)的状态发生改变(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好的抵御变化。使用面向对象技术,可以将这种依赖关系弱化,并能形成一定的依赖关系,从而实现软件体系结构的松耦合。
二、解决方案
拥有一些值得关注的状态的对象通常被称为目标,由于它要将自身的状态改变通知给其他对象,我们也将其称为发布者(publisher
)。所有希望关注发布者状态变化的其他对象被称为订阅者(subscribers
)。
观察者模式建议你为发布者类添加订阅机制,让每个对象都能订阅或取消订阅发布者事件流。 不要害怕!这并不像听上去那么复杂。实际上,该机制包括1)一个用于存储订阅者对象引用的列表成员变量;2)几个用于添加或删除该列表中订阅者的公有方法。
现在,无论何时发生了重要的发布者事件,它都要遍历订阅者并调用其对象的特定通知方法。
实际应用中可能会有十几个不同的订阅者类跟踪着同一个发布者类的事件,你不会希望发布者与所有这些类相耦合的。此外如果他人会使用发布者类,那么你甚至可能会对其中的一些类一无所知。
因此,所有订阅者都必须实现同样的接口,发布者仅通过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据。
使用面向对象的抽象,Observer
模式使得可独立的改变目标与观察者,从而使得二者之间的依赖关系达致松耦合。目标发送通知的时候,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。
三、观察者模式的结构
- 发布者(
Publisher
)会向其他对象发送值得关注的事件。事件会在发布者自身状态改变或执行特定行为后发生。发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。 - 当新事件发生时,发送者会遍历订阅列表并调用每个订阅者对象的通知方法。该方法是在订阅者接口中声明的。
- 订阅者(
Subscriber
)接口声明了通知接口。在绝大多数情况下,该接口仅包含一个update
更新方法。该方法可以拥有多个参数,使发布者能在更新时传递事件的详细信息。 - 具体订阅者(
Concrete Subscribers
)可以执行一些操作来回应发布者的通知。所有具体订阅者类都实现了同样的接口,因此发布者不需要与具体类相耦合。 - 订阅者通常需要一些上下文信息来正确地处理更新。因此,发布者通常会将一些上下文数据作为通知方法的参数进行传递。发布者也可将自身作为参数进行传递,使订阅者直接获取所需的数据。
- 客户端(
Client
)会分别创建发布者和订阅者对象,然后为订阅者注册发布者更新。
四、Observer
代码示例
1 |
|
五、使用场景
- 当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时,可使用观察者模式。
当你使用图形用户界面类时通常会遇到一个问题。比如,你创建了自定义按钮类并允许客户端在按钮中注入自定义代码,这样当用户按下按钮时就会触发这些代码。观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。你可在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码。
当应用中的一些对象必须观察其他对象时,可使用该模式。但仅能在有限时间内或特定情况下使用。
订阅列表是动态的, 因此订阅者可随时加入或离开该列表。
六、优缺点
- 开闭原则。你无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)。
- 你可以在运行时建立对象之间的联系。
订阅者的通知顺序是随机的。
七、与其它模式的关系
责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
- 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
- 命令在发送者和请求者之间建立单向连接。
- 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
- 观察者允许接收者动态地订阅或取消接收请求。
中介者和观察者之间的区别往往很难记住。在大部分情况下,你可以使用其中一种模式, 而有时可以同时使用。让我们来看看如何做到这一点。
中介者的主要目标是消除一系列系统组件之间的相互依赖。这些组件将依赖于同一个中介者对象。观察者的目标是在对象之间建立动态的单向连接,使得部分对象可作为其他对象的附属发挥作用。
有一种流行的中介者模式实现方式依赖于观察者。中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。当中介者以这种方式实现时,它可能看上去与观察者非常相似。
当你感到疑惑时,记住可以采用其他方式来实现中介者。例如,你可永久性地将所有组件链接到同一个中介者对象。这种实现方式和观察者并不相同,但这仍是一种中介者模式。
假设有一个程序,其所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序中就没有中心化的中介者对象,而只有一些分布式的观察者。