调查者形式
亦称: 事情订阅者、监听者、Event-Subscriber、Listener、Observer
意图
调查者形式是一种行为规划形式, 答应你界说一种订阅机制, 可在方针事情产生时告诉多个 “调查” 该方针的其他方针。
问题
假如你有两种类型的方针: 顾客
和 商铺
。 顾客对某个特定品牌的产品十分感爱好 (例如最新类型的 iPhone 手机), 而该产品很快将会在商铺里出售。
顾客能够每天来商铺看看产品是否到货。 但假如产品尚未到货时, 绝大多数来到商铺的顾客都会空手而归。
前往商铺和发送垃圾邮件
另一方面, 每次新产品到货时, 商铺能够向一切顾客发送邮件 (或许会被视为垃圾邮件)。 这样, 部分顾客就无需重复前往商铺了, 但也或许会惹恼对新产品没有爱好的其他顾客。
咱们好像遇到了一个对立: 要么让顾客浪费时间检查产品是否到货, 要么让商铺浪费资源去告诉没有需求的顾客。
解决方案
具有一些值得重视的状况的方针一般被称为方针, 因为它要将本身的状况改动告诉给其他方针, 咱们也将其称为发布者 (publisher)。 一切期望重视发布者状况改变的其他方针被称为订阅者 (subscribers)。
调查者形式主张你为发布者类增加订阅机制, 让每个方针都能订阅或撤销订阅发布者事情流。 不要害怕! 这并不像听上去那么杂乱。 实践上, 该机制包括
1) 一个用于存储订阅者方针引证的列表成员变量;
2) 几个用于增加或删去该列表中订阅者的公有办法;
订阅机制答应方针订阅事情告诉。
现在, 不管何时产生了重要的发布者事情, 它都要遍历订阅者并调用其方针的特定告诉办法。
实践运用中或许会有十几个不同的订阅者类盯梢着同一个发布者类的事情, 你不会期望发布者与一切这些类相耦合的。 此外假如别人会运用发布者类, 那么你乃至或许会对其中的一些类一无所知。
因而, 一切订阅者都有必要完结相同的接口, 发布者仅经过该接口与订阅者交互。 接口中有必要声明告诉办法及其参数, 这样发布者在发出告诉时还能传递一些上下文数据。
发布者调用订阅者方针中的特定告诉办法来告诉订阅者。
假如你的运用中有多个不同类型的发布者, 且期望订阅者可兼容一切发布者, 那么你乃至能够进一步让一切发布者遵循相同的接口。 该接口仅需描述几个订阅办法即可。 这样订阅者就能在不与详细发布者类耦合的情况下经过接口调查发布者的状况。
真实国际类比
杂志和报纸订阅。
假如你订阅了一份杂志或报纸, 那就不需求再去报摊查询新出版的刊物了。 出版社 (即运用中的 “发布者”) 会在刊物出版后 (乃至提前) 直接将最新一期寄送至你的邮箱中。
出版社负责保护订阅者列表, 了解订阅者对哪些刊物感爱好。 当订阅者期望出版社停止寄送新一期的杂志时, 他们可随时从该列表中退出。
调查者形式结构
- 发布者 (Publisher) 会向其他方针发送值得重视的事情。 事情会在发布者本身状况改动或履行特定行为后产生。 发布者中包括一个答应新订阅者加入和当时订阅者脱离列表的订阅构架。
- 当新事情产生时, 发送者会遍历订阅列表并调用每个订阅者方针的告诉办法。 该办法是在订阅者接口中声明的。
-
订阅者 (Subscriber) 接口声明晰告诉接口。 在绝大多数情况下, 该接口仅包括一个
update
更新办法。 该办法能够具有多个参数, 使发布者能在更新时传递事情的详细信息。 - 详细订阅者 (Concrete Subscribers) 能够履行一些操作来回应发布者的告诉。 一切详细订阅者类都完结了相同的接口, 因而发布者不需求与详细类相耦合。
- 订阅者一般需求一些上下文信息来正确地处理更新。 因而, 发布者一般会将一些上下文数据作为告诉办法的参数进行传递。 发布者也可将本身作为参数进行传递, 使订阅者直接获取所需的数据。
- 客户端 (Client) 会别离创立发布者和订阅者方针, 然后为订阅者注册发布者更新。
伪代码
在本例中, 调查者形式答应文本编辑器方针将本身的状况改动告诉给其他服务方针。
将方针中产生的事情告诉给其他方针。
订阅者列表是动态生成的: 方针可在运行时根据程序需求开端或停止监听告诉。
在本完结中, 编辑器类本身并不保护订阅列表。 它将工作委派给专门从事此工作的一个特殊辅佐方针。 你还可将该方针晋级为中心化的事情分发器, 答应任何方针成为发布者。
只要发布者经过相同的接口与一切订阅者进行交互, 那么在程序中新增订阅者时就无需修改已有发布者类的代码。
调查者形式适合运用场景
当一个方针状况的改动需求改动其他方针, 或实践方针是事先不知道的或动态改变的时, 可运用调查者形式。
当你运用图形用户界面类时一般会遇到一个问题。 比如, 你创立了自界说按钮类并答应客户端在按钮中注入自界说代码, 这样当用户按下按钮时就会触发这些代码。
调查者形式答应任何完结了订阅者接口的方针订阅发布者方针的事情告诉。 你可在按钮中增加订阅机制, 答应客户端经过自界说订阅类注入自界说代码。
当运用中的一些方针有必要调查其他方针时, 可运用该形式。 但仅能在有限时间内或特定情况下运用。
订阅列表是动态的, 因而订阅者可随时加入或脱离该列表。
完结办法
-
仔细检查你的事务逻辑, 试着将其拆分为两个部分: 独立于其他代码的中心功能将作为发布者; 其他代码则将转化为一组订阅类。
-
声明订阅者接口。 该接口至少应声明一个
update
办法。 -
声明发布者接口并界说一些接口来在列表中增加和删去订阅方针。 记住发布者有必要仅经过订阅者接口与它们进行交互。
-
确认寄存实践订阅列表的方位并完结订阅办法。 一般一切类型的发布者代码看上去都一样, 因而将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 详细发布者会扩展该类然后继承一切的订阅行为。
可是, 假如你需求在现有的类层次结构中运用该形式, 则能够考虑运用组合的办法: 将订阅逻辑放入一个独立的方针, 然后让一切实践订阅者运用该方针。
-
创立详细发布者类。 每次发布者产生了重要事情时都有必要告诉一切的订阅者。
-
在详细订阅者类中完结告诉更新的办法。 绝大部分订阅者需求一些与事情相关的上下文数据。 这些数据可作为告诉办法的参数来传递。
但还有另一种选择。 订阅者接纳到告诉后直接从告诉中获取一切数据。 在这种情况下, 发布者有必要经过更新办法将本身传递出去。 另一种不太灵敏的办法是经过构造函数将发布者与订阅者永久性地衔接起来。
-
客户端有必要生成所需的全部订阅者, 并在相应的发布者处完结注册工作。
调查者形式优缺点
-
开闭原则。 你无需修改发布者代码就能引进新的订阅者类 (假如是发布者接口则可轻松引进发布者类)。
-
你能够在运行时树立方针之间的联系。
-
订阅者的告诉次序是随机的。
与其他形式的关系
-
责任链形式
、指令形式
、中介者形式
和调查者形式
用于处理恳求发送者和接纳者之间的不同衔接办法:- 责任链依照次序将恳求动态传递给一系列的潜在接纳者, 直至其中一名接纳者对恳求进行处理。
- 指令在发送者和恳求者之间树立单向衔接。
- 中介者清除了发送者和恳求者之间的直接衔接, 强制它们经过一个中介方针进行间接交流。
- 调查者答应接纳者动态地订阅或撤销接纳恳求。
-
中介者和调查者 之间的区别往往很难记住。 在大部分情况下, 你能够运用其中一种形式, 而有时能够一起运用。 让咱们来看看怎么做到这一点。
中介者的主要方针是消除一系列体系组件之间的相互依赖。 这些组件将依赖于同一个中介者方针。 调查者的方针是在方针之间树立动态的单向衔接, 使得部分方针可作为其他方针的附属发挥作用。
有一种盛行的中介者形式完结办法依赖于调查者。 中介者方针担当发布者的角色, 其他组件则作为订阅者, 能够订阅中介者的事情或撤销订阅。 当中介者以这种办法完结时, 它或许看上去与调查者十分类似。
当你感到疑惑时, 记住能够选用其他办法来完结中介者。 例如, 你可永久性地将一切组件链接到同一个中介者方针。 这种完结办法和调查者并不相同, 但这仍是一种中介者形式。
假设有一个程序, 其一切的组件都变成了发布者, 它们之间能够相互树立动态衔接。 这样程序中就没有中心化的中介者方针, 而只有一些分布式的调查者。
代码示例
- Go 调查者形式解说和代码示例 – 掘金 ()