日期: 2020-06-22 17:49:38
本文实例讲述了JavaScript设计模式之观察者模式与发布订阅模式。分享给大家供大家参考,具体如下:
学习了一段时间设计模式,当学到观察者模式和发布订阅模式的时候遇到了很大的问题,这两个模式有点类似,有点傻傻分不清楚,博客起因如此,开始对观察者和发布订阅开始了Google
之旅。对整个学习过程做一个简单的记录。
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern
)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。在观察模式中共存在两个角色观察者(Observer)
与被观察者(Subject)
,然而观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。
其实观察者模式是一个或多个观察者对目标的状态感兴趣,它们通过将自己依附在目标对象之上以便注册所感兴趣的内容。目标状态发生改变并且观察者可能对这些改变感兴趣,就会发送一个通知消息,调用每个观察者的更新方法。当观察者不再对目标状态感兴趣时,它们可以简单的将自己从中分离。
在观察者模式中一共分为这么几个角色:
举一个生活中的例子,公司老板可以为下面的工作人员分配认为,如果老板作为被观察者而存在,那么下面所属的那些员工则就作为观察者而存在,为工作人员分配的任务来通知下面的工作人员应该去做哪些工作。
通过上面的例子可以对观察者模式有一个简单的认知,接下来结合下面的这张图来再次分析一下上面的例子。
如果Subject = 老板
的话,那么Observer N = 工作人员
,如果细心观察的话会发现下图中莫名到的多了一个notify()
,那么上述例子中的工作就是notify()
。
既然各个关系已经屡清楚了,下面通过代码来实现一下上述的例子:
// 观察者队列 class ObserverList{ constructor(){ this.observerList = {}; } Add(obj,type = "any"){ if(!this.observerList[type]){ this.observerList[type] = []; } this.observerList[type].push(obj); } Count(type = "any"){ return this.observerList[type].length; } Get(index,type = "any"){ let len = this.observerList[type].length; if(index > -1 && index < len){ return this.observerList[type][index] } } IndexOf(obj,startIndex,type = "any"){ let i = startIndex, pointer = -1; let len = this.observerList[type].length; while(i < len){ if(this.observerList[type][i] === obj){ pointer = i; } i++; } return pointer; } RemoveIndexAt(index,type = "any"){ let len = this.observerList[type].length; if(index === 0){ this.observerList[type].shift(); } else if(index === len-1){ this.observerList[type].pop(); } else{ this.observerList[type].splice(index,1); } } } // 老板 class Boos { constructor(){ this.observers = new ObserverList(); } AddObserverList(observer,type){ this.observers.Add(observer,type); } RemoveObserver(oberver,type){ let i = this.observers.IndexOf(oberver,0,type); (i != -1) && this.observers.RemoveIndexAt(i,type); } Notify(type){ let oberverCont = this.observers.Count(type); for(let i=0;i<oberverCont;i++){ let emp = this.observers.Get(i,type); emp && emp(type); } } } class Employees { constructor(name){ this.name = name; } getName(){ return this.name; } } class Work { married(name){ console.log(`${name}上班`); } unemployment(name){ console.log(`${name}出差`); } writing(name){ console.log(`${name}写作`); } writeCode(name){ console.log(`${name}打代码`); } } let MyBoos = new Boos(); let work = new Work(); let aaron = new Employees("Aaron"); let angie = new Employees("Angie"); let aaronName = aaron.getName(); let angieName = angie.getName(); MyBoos.AddObserverList(work.married,aaronName); MyBoos.AddObserverList(work.writeCode,aaronName); MyBoos.AddObserverList(work.writing,aaronName); MyBoos.RemoveObserver(work.writing,aaronName); MyBoos.Notify(aaronName); MyBoos.AddObserverList(work.married,angieName); MyBoos.AddObserverList(work.unemployment,angieName); MyBoos.Notify(angieName); // Aaron上班 // Aaron打代码 // Angie上班 // Angie出差
代码里面完全遵循了流程图,Boos
类作为被观察者而存在,Staff
作为观察者,通过Work
两者做关联。
如果相信的阅读上述代码的话可以出,其实观察者的核心代码就是peopleList
这个对象,这个对象里面存放了N
多个Array
数组,通过run
方法触发观察者的notify
队列。观察者模式主要解决的问题就是,一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。当我们在做程序设计的时候,当一个目标对象的状态发生改变,所有的观察者对象都将得到通知,进行广播通知的时候,就可以使用观察者模式啦。
对于观察者模式在被观察者中有一个用于存储观察者对象的list
队列,通过统一的方法触发,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。
在发布订阅模式上卡了很久,但是废了好长时间没有搞明白,也不知道自己的疑问在哪,于是就疯狂Google
不断地翻阅找到自己的疑问,个人觉得如果想要搞明白发布订阅模式首先要搞明白谁是发布者,谁是订阅者。
发布订阅:在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。-- 维基百科
看了半天没整明白(✿◡‿◡),惭愧...于是,学习的路途不能止步,继续...
大概很多人都和我一样,觉得发布订阅模式里的Publisher
,就是观察者模式里的Subject
,而Subscriber
,就是Observer
。Publisher
变化时,就主动去通知Subscriber
。其实并不是。在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。互不相识?那他们之间如何交流?
答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker
。
发布者只需告诉Broker
,我要发的消息,topic
是AAA
,订阅者只需告诉Broker
,我要订阅topic
是AAA
的消息,于是,当Broker
收到发布者发过来消息,并且topic
是AAA
时,就会把消息推送给订阅了topic
是AAA
的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。
也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。
通过上面的描述终于有了一些眉目,再举一个生活中的例子,就拿微信公众号来说,每次微信公众号推送消息并不是一下子推送给微信的所有用户,而是选择性的推送给那些已经订阅了该公众号的人。
老规矩吧,用代码实现一下:
class Utils { constructor(){ this.observerList = {}; } Add(obj,type = "any"){ if(!this.observerList[type]){ this.observerList[type] = []; } this.observerList[type].push(obj); } Count(type = "any"){ return this.observerList[type].length; } Get(index,type = "any"){ let len = this.observerList[type].length; if(index > -1 && index < len){ return this.observerList[type][index] } } IndexOf(obj,startIndex,type = "any"){ let i = startIndex, pointer = -1; let len = this.observerList[type].length; while(i < len){ if(this.observerList[type][i] === obj){ pointer = i; } i++; } return pointer; } } // 订阅者 class Subscribe extends Utils {}; // 发布者 class Publish extends Utils {}; // 中转站 class Broker { constructor(){ this.publish = new Publish(); this.subscribe = new Subscribe(); } // 订阅 Subscribe(fn,key){ this.subscribe.Add(fn,key); } // 发布 Release(fn,key){ this.publish.Add(fn,key); } Run(key = "any"){ let publishList = this.publish.observerList; let subscribeList = this.subscribe.observerList; if(!publishList[key] || !subscribeList[key]) throw "No subscribers or published messages"; let pub = publishList[key]; let sub = subscribeList[key]; let arr = [...pub,...sub]; while(arr.length){ let item = arr.shift(); item(key); } } } class Employees { constructor(name){ this.name = name; } getName(){ let {name} = this; return name; } receivedMessage(key,name){ console.log(`${name}收到了${key}发来的消息`); } } class Public { constructor(name){ this.name = name; } getName(){ let {name} = this; return name; } sendMessage(key){ console.log(`${key}发送了一条消息`); } } let broker = new Broker(); let SundayPublic = new Public("Sunday"); let MayPublic = new Public("May"); let Angie = new Employees("Angie"); let Aaron = new Employees("Aaron"); broker.Subscribe(() => { Angie.receivedMessage(SundayPublic.getName(),Angie.getName()); },SundayPublic.getName()); broker.Subscribe(() => { Angie.receivedMessage(SundayPublic.getName(),Aaron.getName()); },SundayPublic.getName()); broker.Subscribe(() => { Aaron.receivedMessage(MayPublic.getName(),Aaron.getName()); },MayPublic.getName()); broker.Release(MayPublic.sendMessage,MayPublic.getName()); broker.Release(SundayPublic.sendMessage,SundayPublic.getName()); broker.Run(SundayPublic.getName()); broker.Run(MayPublic.getName()); // Sunday发送了一条消息 // Angie收到了Sunday发来的消息 // Aaron收到了Sunday发来的消息 // May发送了一条消息 // Aaron收到了May发来的消息
通过上面的输出结果可以得出,只要订阅过该公众号的用户,只要公众号发送一条消息,所有订阅过该条消息的用户都是可以收到这条消息。虽然代码有点多,但是确确实实能够体现发布订阅模式的魅力,很不错。
发布订阅模式可以降低发布者与订阅者之间的耦合程度,两者之间从来不关系你是谁,你要作什么?订阅者只需要跟随发布者,若发布者发生变化就会通知订阅者应该也做出相对于的变化。发布者与订阅者之间不存在直接通信,他们所有的一切事情都是通过中介者相互通信,它过滤所有传入的消息并相应地分发它们。发布订阅模式可用于在不同系统组件之间传递消息的模式,而这些组件不知道关于彼此身份的任何信息。
Observer
模式中,Observers
知道Subject
,同时Subject
还保留了Observers
的记录。然而,在发布者/订阅者中,发布者和订阅者不需要彼此了解。他们只是在消息队列或代理的帮助下进行通信。Publisher / Subscriber
模式中,组件是松散耦合的,而不是Observer
模式。Subject
调用其所有观察者的适当方法。发布者/订阅者在大多情况下是异步方式(使用消息队列)。如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的。如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。发布订阅模式相比观察者模式多了个事件通道,订阅者和发布者不是直接关联的。目标和观察者是直接联系在一起的。观察者把自身添加到了目标对象中,可见和发布订阅模式差别还是很大的。在这种模式下,目标更像一个发布者,他让添加进来的所有观察者都执行了传入的函数,而观察者就像一个订阅者。虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。
虽然在学习这两种模式的时候有很多的坎坷,最终还是按照自己的理解写出来了两个案例。或许理解的有偏差,如果哪里有问题,希望大家在下面留言指正,我会尽快做出修复的。
感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。
更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述对大家JavaScript程序设计有所帮助。