观察者模式有一个“别名”,叫发布 - 订阅模式(之所以别名加了引号,是因为两者之间存在着细微的差异,下个小节里我们会讲到这点)。这个别名非常形象地诠释了观察者模式里两个核心的角色要素——“发布者”与“订阅者”。
在上述的过程中,需求文档(目标对象)的发布者只有一个——产品经理韩梅梅。而需求信息的接受者却有多个——前端、后端、测试同学,这些同学的共性就是他们需要根据需求信息开展自己后续的工作、因此都非常关心这个需求信息,于是不得不时刻关注着这个群的群消息提醒,他们是实打实的订阅者,即观察者对象。
现在我们再回过头来看一遍开头我们提到的略显抽象的定义:
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
在我们上文这个钉钉群里,一个需求信息对象对应了多个观察者(技术同学),当需求信息对象的状态发生变化(从无到有)时,产品经理通知了群里的所有同学,以便这些同学接收信息进而开展工作:角色划分 —> 状态变化 —> 发布者通知到订阅者,这就是观察者模式的“套路”。
在实践中理解定义
结合我们上面的分析,现在大家知道,在观察者模式里,至少应该有两个关键角色是一定要出现的——发布者和订阅者。用面向对象的方式表达的话,那就是要有两个类。
首先我们来看这个代表发布者的类,我们给它起名叫Publisher。这个类应该具备哪些“基本技能”呢?大家回忆一下上文中的韩梅梅,韩梅梅的基本操作是什么?首先是拉群(增加订阅者),然后是@所有人(通知订阅者),这俩是最明显的了。此外作为群主&产品经理,韩梅梅还具有踢走项目组成员(移除订阅者)的能力。OK,产品经理发布者类的三个基本能力齐了,下面我们开始写代码:
// 定义发布者类class Publisher {constructor() {this.observers = []console.log('Publisher created')}// 增加订阅者add(observer) {console.log('Publisher.add invoked')this.observers.push(observer)}// 移除订阅者remove(observer) {console.log('Publisher.remove invoked')this.observers.forEach((item, i) => {if (item === observer) {this.observers.splice(i, 1)}})}// 通知所有订阅者notify() {console.log('Publisher.notify invoked')this.observers.forEach((observer) => {observer.update(this)})}}
ok,搞定了发布者,我们一起来想想订阅者能干啥——其实订阅者的能力非常简单,作为被动的一方,它的行为只有两个——被通知、去执行(本质上是接受发布者的调用,这步我们在Publisher中已经做掉了)。既然我们在Publisher中做的是方法调用,那么我们在订阅者类里要做的就是方法的定义:
// 定义订阅者类class Observer {constructor() {console.log('Observer created')}update() {console.log('Observer.update invoked')}}
以上,我们就完成了最基本的发布者和订阅者类的设计和编写。在实际的业务开发中,我们所有的定制化的发布者/订阅者逻辑都可以基于这两个基本类来改写。比如我们可以通过拓展发布者类,来使所有的订阅者来监听某个特定状态的变化。仍然以开篇的例子为例,我们让开发者们来监听需求文档(prd)的变化:
// 定义一个具体的需求文档(prd)发布类class PrdPublisher extends Publisher {constructor() {super()// 初始化需求文档this.prdState = null// 韩梅梅还没有拉群,开发群目前为空this.observers = []console.log('PrdPublisher created')}// 该方法用于获取当前的prdStategetState() {console.log('PrdPublisher.getState invoked')return this.prdState}// 该方法用于改变prdState的值setState(state) {console.log('PrdPublisher.setState invoked')// prd的值发生改变this.prdState = state// 需求文档变更,立刻通知所有开发者this.notify()}}
作为订阅方,开发者的任务也变得具体起来:接收需求文档、并开始干活:
class DeveloperObserver extends Observer {constructor() {super()// 需求文档一开始还不存在,prd初始为空对象this.prdState = {}console.log('DeveloperObserver created')}// 重写一个具体的update方法update(publisher) {console.log('DeveloperObserver.update invoked')// 更新需求文档this.prdState = publisher.getState()// 调用工作函数this.work()}// work方法,一个专门搬砖的方法work() {// 获取需求文档const prd = this.prdState// 开始基于需求文档提供的信息搬砖。。。...console.log('996 begins...')}}
下面,我们可以 new 一个 PrdPublisher 对象(产品经理),她可以通过调用 setState 方法来更新需求文档。需求文档每次更新,都会紧接着调用 notify 方法来通知所有开发者,这就实现了定义里所谓的:
目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
OK,下面我们来看看韩梅梅和她的小伙伴们是如何搞事情的吧:
// 创建订阅者:前端开发李雷const liLei = new DeveloperObserver()// 创建订阅者:服务端开发小A(sorry。。。起名字真的太难了)const A = new DeveloperObserver()// 创建订阅者:测试同学小Bconst B = new DeveloperObserver()// 韩梅梅出现了const hanMeiMei = new PrdPublisher()// 需求文档出现了const prd = {// 具体的需求内容...}// 韩梅梅开始拉群hanMeiMei.add(liLei)hanMeiMei.add(A)hanMeiMei.add(B)// 韩梅梅发送了需求文档,并@了所有人hanMeiMei.setState(prd)
以上,就是观察者模式在代码世界里的完整实现流程了。
