观察者
意图
观察者是一种行为设计模式,允许您定义订阅机制,以通知多个对象发生在它们正在观察的对象上的任何事件。

问题
假设有两种类型的对象:a客户
和一个商店
.顾客对某一特定品牌的产品非常感兴趣(比如说,这是一款新款iPhone),而这款产品很快就会在商店里上市。
客户可以每天访问商店并查看产品的可用性。但是,虽然产品仍在途中,但大多数旅行都是毫无意义的。

访问商店vs.发送垃圾邮件
另一方面,每当新产品上市时,商店可能会向所有客户发送大量电子邮件(可能被视为垃圾邮件)。这将使一些顾客不必无休止地去商店。同时,这也会让其他对新产品不感兴趣的客户感到不安。
看来我们有矛盾了。要么是客户浪费时间检查产品的可用性,要么是商店浪费资源通知错误的客户。
解决方案
具有某种有趣状态的对象通常被称为对象主题,但由于它也将通知其他对象关于其状态的变化,我们将调用它出版商.所有其他希望跟踪发布者状态更改的对象都将被调用用户.
观察者模式建议您向发布者类添加订阅机制,以便各个对象可以订阅或取消订阅来自该发布者的事件流。不要害怕!事情并不像听起来那么复杂。实际上,这种机制由1)一个数组字段组成,用于存储订阅方对象的引用列表;2)几个公共方法,用于向该列表中添加订阅方和从该列表中删除订阅方。

订阅机制允许单个对象订阅事件通知。
现在,每当发布者发生重要事件时,它都会遍历其订阅者并在订阅者的对象上调用特定的通知方法。
真实的应用程序可能有几十个不同的订阅者类,它们对跟踪同一发布者类的事件感兴趣。您不希望将发布者与所有这些类绑定在一起。此外,如果您的发布者类应该由其他人使用,那么您甚至可能事先不知道其中一些。
这就是为什么所有订阅者实现相同的接口以及发布者仅通过该接口与他们通信是至关重要的。该接口应该声明通知方法以及一组参数,发布者可以使用这些参数将一些上下文数据与通知一起传递。

发布者通过调用订阅者对象上的特定通知方法来通知订阅者。
如果你的应用程序有几种不同类型的发布者,你想让你的订阅者与所有的发布者兼容,你可以更进一步,让所有的发布者遵循相同的界面。这个接口只需要描述几个订阅方法。该接口将允许订阅者观察发布者的状态,而无需与发布者的具体类耦合。
真实的模拟

订阅杂志和报纸。
如果你订阅了一份报纸或杂志,你就不再需要去商店查看下一期是否有货了。相反,出版商会在新书出版后直接发送到你的邮箱,甚至提前发送。
出版商有一份订阅者名单,知道他们对哪些杂志感兴趣。订阅者可以在任何时候离开名单,如果他们希望出版商停止向他们发送新杂志。
结构


的出版商向其他对象发出感兴趣的事件。这些事件发生在发布者更改其状态或执行某些行为时。发布者包含一个订阅基础结构,允许新订阅者加入列表,当前订阅者离开列表。
当发生新事件时,发布者遍历订阅列表,并在每个订阅对象上调用订阅方接口中声明的通知方法。
的订阅者Interface声明通知接口。在大多数情况下,它由单个
更新
方法。该方法可能有几个参数,让发布者在更新时传递一些事件细节。具体的用户执行一些操作以响应发布者发出的通知。所有这些类都必须实现相同的接口,这样发布者就不会耦合到具体的类。
通常,订阅者需要一些上下文信息才能正确地处理更新。出于这个原因,发布者经常将一些上下文数据作为通知方法的参数传递。发布者可以将自身作为参数传递,让订阅者直接获取所需的数据。
的客户端分别创建发布者和订阅者对象,然后为发布者更新注册订阅者。
伪代码
在本例中,观察者模式允许文本编辑器对象将其状态的更改通知其他服务对象。

通知对象发生在其他对象上的事件。
订阅者列表是动态编译的:对象可以在运行时开始或停止侦听通知,这取决于应用程序所需的行为。
在这个实现中,编辑器类本身并不维护订阅列表。它将此工作委托给专门用于此工作的特殊帮助对象。您可以将该对象升级为集中式事件分派器,让任何对象充当发布者。
向程序添加新的订阅者不需要更改现有的发布者类,只要它们通过相同的接口与所有订阅者一起工作即可。
//基本发布者类包括订阅管理代码和通知方法。类EventManager是私有字段监听器:事件类型和监听器方法订阅(eventType,监听器)的哈希映射是监听器。add(eventType, listener)方法取消订阅(eventType, listener)是监听器。remove(eventType, listener) method notify(eventType, data) is foreach (listener .of(eventType)中的监听器)do listener.update(data) //具体发布者包含一些订阅者感兴趣的真实业务逻辑。我们可以从基本发布者派生这个类//,但在//现实生活中并不总是可以这样做,因为具体的发布者可能已经是//子类。在这种情况下,您可以将订阅逻辑//与复合修补在一起,就像我们在这里所做的那样。class Editor是公共字段事件:EventManager是私有字段文件:文件构造器Editor() is events = new EventManager() //业务逻辑的方法可以通知订阅者//更改。方法openFile(path)是这样的。file = new文件(路径)事件。notify("open", file.name)方法saveFile()是file.write()事件。Notify ("save", file.name) //…//这是订阅者界面。如果您的编程语言//支持函数类型,则可以将整个//订阅者层次结构替换为一组函数。 interface EventListener is method update(filename) // Concrete subscribers react to updates issued by the publisher // they are attached to. class LoggingListener implements EventListener is private field log: File private field message: string constructor LoggingListener(log_filename, message) is this.log = new File(log_filename) this.message = message method update(filename) is log.write(replace('%s',filename,message)) class EmailAlertsListener implements EventListener is private field email: string private field message: string constructor EmailAlertsListener(email, message) is this.email = email this.message = message method update(filename) is system.email(email, replace('%s',filename,message)) // An application can configure publishers and subscribers at // runtime. class Application is method config() is editor = new Editor() logger = new LoggingListener( "/path/to/log.txt", "Someone has opened the file: %s") editor.events.subscribe("open", logger) emailAlerts = new EmailAlertsListener( "admin@example.com", "Someone has changed the file: %s") editor.events.subscribe("save", emailAlerts)
适用性
当对一个对象状态的更改可能需要更改其他对象,并且实际的对象集事先未知或动态更改时,请使用Observer模式。
在使用图形用户界面的类时,您经常会遇到这个问题。例如,您创建了自定义按钮类,并且希望让客户端将一些自定义代码挂接到您的按钮上,以便在用户按下按钮时触发。
观察者模式允许任何实现订阅者接口的对象订阅发布者对象中的事件通知。您可以将订阅机制添加到按钮中,让客户端通过自定义订阅者类连接它们的自定义代码。
当应用程序中的某些对象必须观察其他对象时,请使用该模式,但仅限于有限的时间或特定的情况。
订阅列表是动态的,因此订阅者可以在需要时加入或离开列表。
如何实施
检查你的业务逻辑,并尝试将其分解为两部分:核心功能,独立于其他代码,将扮演发行者的角色;其余部分将转换为一组订阅者类。
声明订阅者接口。至少,它应该声明一个single
更新
方法。声明发布者接口并描述一对方法,用于向列表中添加订阅者对象和从列表中删除该对象。请记住,发布者必须仅通过订阅者接口与订阅者合作。
决定在哪里放置实际的订阅列表和订阅方法的实现。通常,对于所有类型的发布者,这段代码看起来都是一样的,因此将其放置在直接从发布者接口派生的抽象类中是很明显的。具体发布者扩展了该类,继承了订阅行为。
但是,如果要将模式应用于现有的类层次结构,请考虑基于组合的方法:将订阅逻辑放入单独的对象中,并使所有真正的发布者都使用它。
创建具体的发布者类。每当发布者内部发生重要事件时,它必须通知所有订阅者。
在具体的订阅者类中实现更新通知方法。大多数订阅者都需要一些关于事件的上下文数据。它可以作为通知方法的参数传递。
但还有另一种选择。在接收到通知时,订阅者可以直接从通知中获取任何数据。在这种情况下,发布者必须通过更新方法传递自身。不太灵活的选择是通过构造函数将发布者永久地链接到订阅者。
客户端必须创建所有必要的订阅者,并将它们注册到适当的发布者。
利与弊
- 打开/关闭原则.您可以引入新的订阅者类,而不必更改发布者的代码(如果有发布者接口,反之亦然)。
- 您可以在运行时建立对象之间的关系。
- 通知订阅者的顺序是随机的。
与其他模式的关系
责任链,命令,中介而且观察者处理连接请求发送方和接收方的各种方式:
- 责任链按顺序将请求沿着潜在接收者的动态链传递,直到其中一个接收者处理它。
- 命令在发送方和接收方之间建立单向连接。
- 中介消除发送方和接收方之间的直接连接,迫使它们通过中介对象间接通信。
- 观察者让接收者动态地订阅和取消订阅接收请求。
两者之间的区别中介而且观察者往往难以捉摸。在大多数情况下,您可以实现这些模式中的任何一种;但有时你可以同时应用这两种方法。我们来看看怎么做。
的主要目标中介就是消除一组系统组件之间的相互依赖关系。相反,这些组件依赖于单个中介对象。的目标观察者是在对象之间建立动态单向连接,其中一些对象充当其他对象的下属。
有一个流行的实现中介模式依赖于观察者.中介对象扮演发布者的角色,组件充当订阅和取消订阅中介事件的订阅者。当中介是这样实现的,它可能看起来很相似观察者.
当您感到困惑时,请记住您可以用其他方式实现Mediator模式。例如,您可以永久地将所有组件链接到同一个中介对象。这个实现不像观察者但仍然是Mediator模式的实例。
现在想象一个程序,其中所有组件都成为发布者,允许彼此之间动态连接。不会有一个集中的中介对象,只有一组分布式的观察者。