关于发布订阅模式:设计模式不到50行实现一个发布订阅模式

什么是公布/订阅者模式?置信大家在看Vue的视图更新以及$emit事件源码都有见到公布订阅的身影。包含Nodejs Events、jQuery中同样都有实现公布订阅模式,用网上一张比拟形象的图: 右边是观察者模式,左边是公布订阅模式:定义有事件名的若干个订阅者汇合进行保护(增删改查),在公布(emit)的时候通过惟一的事件名(key)把对应的订阅者汇合中的办法一一顺次执行一遍。 具体实现 var eventEmitter = { list: {}, //订阅主题 on: function (event, fn) { if (typeof fn !== "function") { return false; } //创立订阅者列表,如果存在就直接插入 (this.list[event] || (this.list[event] = [])).push(fn); return this; }, //公布主题 emit: function () { var event = [].shift.call(arguments); if (this.list[event] && this.list[event].length) { var fns = this.list[event].slice(); //浅拷贝后间接对列表所有订阅者函数顺次执行 for (var i in fns) { this.list[event][i].apply(this, arguments); } return this; } return false; },}on和emit的话比较简单首先定义一个list对象用于寄存事件的汇合的映射表当调用on事件绑定的时候通过传入的事件名判断以后是否已存在list中,不存在则先设置一个空数组,否则就间接push进去。 ...

February 13, 2022 · 1 min · jiezi

JavaScript中发布订阅模式观察者模式

发布/订阅模式的前身-观察者模式观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。 观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。 什么是发布/订阅模式其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。 根本作用广泛应用于异步编程中(替代了传递回调函数)对象之间松散耦合的编写代码基本案例介绍背景:成都老妈兔头真香,买的人太多需要预定才能买到,所以顾客就等于了订阅者,订阅老妈兔头。而老妈兔头有货了得通知顾客来买啊,不然没有钱赚,得通知所有的订阅者有货了来提兔头,这时老妈兔头这家店就是发布者。 /*兔头店*/ var shop={ listenList:[],//缓存列表 addlisten:function(fn){//增加订阅者 this.listenList.push(fn); }, trigger:function(){//发布消息 for(var i=0,fn;fn=this.listenList[i++];){ fn.apply(this,arguments); } }}/*小明订阅了商店*/shop.addlisten(function(taste){ console.log("通知小明,"+taste+"味道的好了");});/*小龙订阅了商店*/shop.addlisten(function(taste){ console.log("通知小龙,"+taste+"味道的好了");});/*小红订阅了商店*/shop.addlisten(function(taste){ console.log("通知小红,"+taste+"味道的好了");}); // 发布订阅shop.trigger("中辣");//console通知小明,中辣味道的好了通知小龙,中辣味道的好了通知小红,中辣味道的好了案例升级上面的案例存在问题,因为在触发的时候是将所以的订阅都触发了,并没有区分和判断,所以需要一个Key来区分订阅的类型,并且根据不同的情况触发。而且订阅是可以取消的。 升级思路: 创建一个对象(缓存列表)addlisten方法用来把订阅回调函数fn都加到缓存列表listenList中trigger方法取到arguments里第一个当做key,根据key值去执行对应缓存列表中的函数remove方法可以根据key值取消订阅/*兔头店*/ var shop={ listenList:{},//缓存对象 addlisten:function(key,fn){ // 没有没有key给个初值避免调用报错 if (!this.listenList[key]) { this.listenList[key] = []; } // 增加订阅者,一个key就是一种订阅类型 this.listenList[key].push(fn); }, trigger:function(){ const key = Array.from(arguments).shift() const fns = this.listenList[key] // 这里排除两种特殊情况,第一种为触发的一种从未订阅的类型,第二种订阅后取消了所有订阅的 if(!fns || fns.length===0){ return false; } // 发布消息,触发同类型的所有订阅, fns.forEach((fn)=>{ fn.apply(this,arguments); })/* for(var i=0,fn;fn=fns[i++];){ fn.apply(this,arguments); } */ }, remove:function(key,fn){ var fns=this.listenList[key];//取出该类型的对应的消息集合 if(!fns){//如果对应的key没有订阅直接返回 return false; } if(!fn){//如果没有传入具体的回掉,则表示需要取消所有订阅 fns && (fns.length=0); }else{ for(var l=fns.length-1;l>=0;l--){//遍历回掉函数列表 if(fn===fns[l]){ // 这里是传入地址的比较,所以不能直接用匿名函数了 fns.splice(l,1);//删除订阅者的回掉 } } } }}function xiaoming(taste){ console.log("通知小明,"+taste+"味道的好了");}function xiaolong(taste){ console.log("通知小龙,"+taste+"味道的好了");}function xiaohong(taste){ console.log("通知小红,"+taste+"味道的好了");}// 小明订阅了商店shop.addlisten('中辣',xiaoming);shop.addlisten('特辣',xiaoming);// 小龙订阅了商店shop.addlisten('微辣',xiaolong);// 小红订阅了商店shop.addlisten('中辣',xiaohong);// 小红突然不想吃了shop.remove("中辣",xiaohong);// 中辣口味做好后,发布订阅shop.trigger("中辣");shop.trigger("微辣");shop.trigger("特辣"); ...

November 2, 2019 · 1 min · jiezi

发布订阅模式和观察者模式真的不一样

背景设计模式的定义: 在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。设计模式并不能直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,它不是一个死的机制,它是一种思想,一种代码的形式。 每种语言对于各种设计模式都要它们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂模式就不适用于JavaSctipt。模式应该用在正确的地方,而所谓正确的地方只有我们深刻理解模式的意图后,再结合项目的实际场景才知道。 观察者模式 (Observer Pattern)观察者模式定义了对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,并自动更新。观察者模式属于行为模式,行为模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。 观察者模式还有一个别名叫“发布-订阅模式”,又或者“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐各通知订阅者。我们用报纸期刊的订阅来举例说明,当你订阅一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是定义里描述的“一对多”的依赖关系。 发布-订阅模式 (Pub-Sub Pattern)其实24种基本设计模式中,并没有发布-订阅模式,上面也解释了,它只是观察者模式的一个别称。但经过时间的沉淀,它已经强大起来,已经独立于观察者模式,成为一种新的设计模式。 在现在的发布-订阅模式中,发布者的消息不会直接发送给订阅者,这意味着发布者和订阅者都不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息,并相应的分发给它们的订阅者。举个例子,你在微博上关注了A,同时其他很多人也关注了A,当A发布动态时,微博就会为你们推送这条动态。A就是发布者,你就是订阅者,微博是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的。 观察者模式和发布-订阅模式有什么区别?我们先来看下者两个模式的实现结构: 观察者模式: 观察者(Observer)直接订阅(Subscribe)主体(Subject),而当主体被激活时,会触发(Fire Event)观察者里的事件。 发布-订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。 案例观察者模式// 有一家猎人公会,其中每个猎人都具体发布任务(publish),订阅任务(subscribe)的功能// 它们都有一个订阅列表记录谁订阅了自己// 定义一个猎人,包括姓名、级别、订阅列表function Hunter(name, level) { this,name = name this.level = level this.list = []}Hunter.prototype.publish = function (money) { console.log(this,level + '猎人: ' + this.name + '寻求帮助') this.list.forEach(function (callback) { callback && callback(money) })}Hunter.prototype.subscribe = function (target, callback) { console.log(this.level + '猎人: ' + this.name + '订阅了: ' + target.name) target.list.push(callback)}// 猎人公会走注册了几个猎人var hunterZhang = new Hunter('张三', '钻石')var hunterLi = new Hunter('李四', '黄金')var hunterWang = new Hunter('王五', '白银')var hunterZhao = new Hunter('赵六', '青铜')// 赵六等级较低,可能需要帮助,所以张三、李四、王五都订阅了赵六hunterZhang.subscribe(hunterZhao, function (money) { console.log('小明表示: ' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')})hunterLi.subscribe(hunterZhao, function () { console.log('李四表示: 给予帮助')})hunterWang.subscribe(hunterZhao, function () { console.log('王五表示: 给予帮助')})// 赵六遇到困难,悬赏198寻求帮助hunterZhao.publish(198)// 猎人们(观察者)关联他们感兴趣的猎人(目标对象),如赵六,当赵六有困难时,会自动通知给他们(观察者)发布-订阅模式// 定义了一家猎人公会// 主要功能包含任务发布大厅(topics)、订阅任务(subscribe)、发布任务(publish)var HunterUnion = { type: 'hunt', topics: Object.create(null), subscribe: function (topic, callback) { if (!this.topics[topic]) { this.topics[topic] = [] } this.topics[topic].push(callback) }, publish: function (topic, money) { if (!this.topics[topic]) { return } for(var cb of this.topics[topic]) { cb(money) } }}// 定义一个猎人类,包括姓名和级别function Hunter(name, level) { this.name = name this.level = level}// 猎人可以在猎人公会发布、订阅任务Hunter.prototype.subscribe = function (task, fn) { console.log(this.level + '猎人: ' + this.name + '订阅了狩猎: ' + task + '的任务') HunterUnion.subscribe(task, fn)}Hunter.prototype.publish = function (task, money) { console.log(this.level + '猎人: ' + this.name + '发布了狩猎: ' + task + '的任务') HunterUnion.publish(task, money)}//猎人工会注册了几个猎人let hunterZhang = new Hunter('张三', '钻石')let hunterLi = new Hunter('李四', '黄金')let hunterWang = new Hunter('王五', '白银')let hunterZhao = new Hunter('赵六', '青铜')//张三,李四,王五分别订阅了狩猎tiger的任务hunterZhang.subscribe('tiger', function(money){ console.log('张三表示:' + (money > 200 ? '' : '不') + '接取任务')})hunterLi.subscribe('tiger', function(money){ console.log('李四表示:接取任务')})hunterWang.subscribe('tiger', function(money){ console.log('王五表示:接取任务')})//赵六订阅了狩猎sheep的任务hunterZhao.subscribe('sheep', function(money){ console.log('赵六表示:接取任务')})//赵六发布了狩猎tiger的任务hunterZhao.publish('tiger', 198)//猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。观察者模式和发布-订阅模式最大的区别: 发布-订阅模式有事件调度中心。 ...

August 28, 2019 · 2 min · jiezi

设计方案浅析观察者模式和发布订阅模式的区别

有时候面试的时候可能会被问到: 观察者模式和发布订阅模式的区别? 没有区别吧? 好像有区别吧? 我们首先来看一下“观察者模式”和“发布订阅模式” 一、观察者设计模式理解设计模式在设计之初的是为了解决什么问题就能很好的在写代码中运用不同的设计模式。 所谓的观察者模式,其实为了实现松耦合(loosely coupled),也就是为了解耦。 当数据有变化有更新的时候,某个方法被调用,这时候就可以更新其他地方需要用到这个数据的地方。 举个例子: 以气象站为例,每当气象站测试数据有更新的时候,changed()方法都会被调用,于是我们在changed()方法中,更新气象仪器上的数据,比如温度、气压等等。 这样写没有毛病,但是,如果我们以后再changed()方法调用的时候,更新更多的信息,比如说湿度,这个时候我们需要去修改changed()方法的代码,这个就是紧耦合的坏处。 那怎么解决这个紧耦合的问题呢? 观察者模式中,changed()方法所在的实例对象,就是被观察者(subject),只需要维护一套观察者(observer)的集合,这些observer实现相同的接口,subject只需要知道:通知观察者(observer)时,需要调用哪一个统一方法就行。 我们再来看一下简单的例子 function Subject(){ this.observerData = [];}Subject.prototype = { add: function(observer) { this.observerData.push(observer); }, remove:function(observer) { var observerData = this.observerData; for(let i=0,length=observerData.length;i<length;i++){ if(observerData[i] === observer){ observerData.splice(i,1) } } }, notify: function() { var observerData = this.observerData; for(let i=0,length=observerData.length;i<length;i++){ observerData[i].update(); } }}function Observer(name){ this.name = name;}Observer.prototype = { update: function() { console.log('hello,' + this.name); }}var sub = new Subject();var obj1 = new Observer('saucxs');var obj2 = new Observer('songEagle');sub.add(obj1);sub.add(obj2);sub.notify();这时候输出的是 ...

May 23, 2019 · 1 min · jiezi

观察者模式与发布订阅模式

观察者模式与发布/订阅模式观察者模式概念一个被观察者的对象,通过注册的方式维护一组观察者对象。当被观察者发生变化,就会产生一个通知,通过广播的方式发送出去,最后调用每个观察者的更新方法。当观察者不再需要接受被观察者的通知时,被观察者可以将该观察者从所维护的组中删除。 实现这个实现包含以下组件: 被观察者:维护一组观察者, 提供用于增加和移除观察者的方法观察者:提供一个更新接口,用于当被观察者状态变化时,得到通知具体的被观察者:状态变化时广播通知给观察者,保持具体的观察者的信息具体的观察者:保持一个指向具体被观察者的引用,实现一个更新接口,用于观察,以便保证自身状态总是和被观察者状态一致的首先,对被观察者维护的一组观察者(列表)进行建模 function ObserverList() { this.observerList = []}ObserverList.prototype.add = function(obj) { return this.observerList.push(obj)}ObserverList.prototype.Empty = function() { this.observerList = []}ObserverList.prototype.removeAt = function(index) { this.observerList.splice(index, 1)}ObserverList.prototype.count = function() { return this.observerList.length}ObserverList.prototype.get = function(index) { if (index > -1 && index < this.observerList.length) { return this.observerList[index] }}// Extend an object with an extensionfunction extend(extension, obj) { for (var key in extension) { obj[key] = extension[key] }}接着,对被观察者以及其增加、删除、通知能力进行建模 function Subject() { this.observers = new ObserverList()}Subject.prototype.addObserver = function(observer) { this.observers.add(observer)}Subject.prototype.removeObserver = function(observer) { this.observers.removeAt(this.observers.IndexOf(observer, 0))}Subject.prototype.notify = function(context) { var observerCount = this.observers.count() for (var i = 0; i < observerCount; i++) { this.observers.get(i).update(context) }}接着,对观察者进行建模,这里的 update 函数之后会被具体的行为覆盖 ...

May 15, 2019 · 3 min · jiezi

前端异步解决方案-2(发布/订阅模式);

什么是发布订阅模式什么是发布订阅模式我这里不多就不多阐述了,给大家提供几个我觉得讲的比较好的博文,请各位自行阅读发布-订阅模式解释 这一篇文章应该是一个java coder写的,但是设计模式这种东西并不分语言,各位可以借鉴一下Javascript中理解发布–订阅模式这一篇是我们前端人写的,但是比较长,大家有耐心可以看看发布中心实现我对发布中心的实现,可以不看(看了能够更好的理解发布订阅模式)//实现发布中心/** log=[{* index: Number, 日志编号(自增)* type: String, 日志类型(‘subscribe’,‘unsubscribe’,‘publish’)* eventName: String, 事件名* time: new Date(), 时间* fun:Function 订阅/取订的方法(只有日志类型为’subscribe’或’unsubscribe’的才有)* param:Object 触发事件时的参数(只有日志类型为’publish’的才有)* }]* eventCenter = {* eventName:[Function,Function] //eventName即为事件名,其值为订阅方法列表* }* .subscribe(eventName, fun) 订阅 eventName:事件名 fun:订阅方法* .unsubscribe(eventName, fun) 取订 eventName:事件名 fun:订阅方法* .publish(eventName[,param]) 发布 eventName:事件名 param:事件参数* .showLog([filter]) 日志展示 filter 过滤器,同数组的过滤器用法 返回过滤后的log* .showEventCenter([eventName]) 事件中心 eventName 事件名 返回事件绑定的方法* /let subscribeCenter = function () { //事件中心 let eventCenter = {}; //日志 let log = []; //添加日志函数 function pushLog(type, eventName, fun, param) { let info = { index: log.length, type: type, eventName: eventName, time: new Date() }; if (fun) { info.fun = fun; } if (param) { info.param = param; } log.push(info) } return { //订阅 subscribe(eventName, fun) { pushLog(“subscribe”, eventName, fun); eventCenter[eventName] = eventCenter[eventName] || []; eventCenter[eventName].push(fun); }, //取消订阅 unsubscribe(eventName, fun) { pushLog(“unsubscribe”, eventName, fun); let onList = eventCenter[eventName]; if (onList) { for (let i = 0; i < onList.length; i++) { if (onList[i] === fun) { onList.splice(i, 1); return } } } }, //发布 publish(eventName, param) { pushLog(“publish”, eventName, null, param) let onList = eventCenter[eventName]; if (onList) { for (let i = 0; i < onList.length; i++) { onListi } } }, //显示日志 showLog(filter) { filter = filter || (() => true); let returnLog = log.filter(filter); returnLog.forEach(x => { let y = {}; for (let key in x) { y[key] = x[key] } return y }); return returnLog; }, //显示事件中心 showEventCenter(eventName) { let selectEM = eventName ? eventCenter[eventName] : eventCenter, returnEM = {}; for (let key in selectEM) { returnEM[key] = []; selectEM[key].forEach(x => { returnEM[key].push(x) }); } return returnEM } }}();如果有看我上一篇文章中事件监听实现的朋友应该对这个不部分代码有一种熟悉的感觉,确实事件监听和发布订阅的实现非常的像。我主要多做的就是一个日志的拓展,保证了每次的动作都可以被监听和查看;发布中心API这个在上面是有的,但是因为上面的部分有一些同学是不看的,所以就在这里摘出来,方便这些同学了解发布中心的用法/* log=[{* index: Number, 日志编号(自增)* type: String, 日志类型(‘subscribe’,‘unsubscribe’,‘publish’)* eventName: String, 事件名* time: new Date(), 时间* fun:Function 订阅/取订的方法(只有日志类型为’subscribe’或’unsubscribe’的才有)* param:Object 触发事件时的参数(只有日志类型为’publish’的才有)* }]* eventCenter = {* eventName:[Function,Function] //eventName即为事件名,其值为订阅方法列表* }* .subscribe(eventName, fun) 订阅 eventName:事件名 fun:订阅方法* .unsubscribe(eventName, fun) 取订 eventName:事件名 fun:订阅方法* .publish(eventName[,param]) 发布 eventName:事件名 param:事件参数* .showLog([filter]) 日志展示 filter 过滤器,同数组的过滤器用法 返回过滤后的log* .showEventCenter([eventName]) 事件中心 eventName 事件名 返回事件绑定的方法* */发布/订阅模式在异步中的应用这里是重点,需要大家仔细看,理解了这段代码不光可以解决异步问题,还可以理解发布订阅者模式是如何应用的;//发布者let f1 = function () { setTimeout(function () { console.log("‘done’ 事件发布 参数:", 123); subscribeCenter.publish(“done”, 123); console.log(“事件中心”, subscribeCenter.showEventCenter()); console.log(“f3 取订 ‘done’”); subscribeCenter.unsubscribe(“done”, f3); setTimeout(function () { console.log("‘done’ 事件发布 参数:", 233); subscribeCenter.publish(“done”, 233); console.log(“事件中心”, subscribeCenter.showEventCenter()); console.log(“日志”, subscribeCenter.showLog()); }, 100) }, 100)};//订阅者let f2 = function (param) { console.log(“f2 is running, param is”, param);};//订阅者let f3 = function (param) { console.log(“f3 is running, param is”, param)};//订阅console.log(“f2 订阅 ‘done’”);subscribeCenter.subscribe(“done”, f2);console.log(“f3 订阅 ‘done’”);subscribeCenter.subscribe(“done”, f3);//发布f1();先贴运行结果在这里可以看到,该模式与事件监听模式非常相似,但是所有的发布都通过了同一个发布中心来控制,这样的话可以方便我们追踪整个事件的状态; ...

April 5, 2019 · 2 min · jiezi

发布订阅模式与观察者模式

背景设计模式并非是软件开发的专业术语,实际上,“模式”最早诞生于建筑学。设计模式的定义是:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。通俗一点说,设计模式是在某种场合下对某个问题的一种解决方案。如果再通俗一点说,设计模式就是给面向对象软件开发中的一些好的设计取个名字。这些“好的设计”并不是谁发明的,而是早已存在于软件开发中。一个稍有经验的程序员也许在不知不觉中数次使用过这些设计模式。GoF(Gang of Four–四人组,《设计模式》几位作者)最大的功绩是把这些“好的设计”从浩瀚的面向对象世界中挑选出来,并且给予它们一个好听又好记的名字。设计模式并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,他不是一个死的机制,他是一种思想,一种写代码的形式。每种语言对于各种设计模式都有他们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂方法模式对于javascript。模式应该用在正确的地方。而哪些才算正确的地方,只有在我们深刻理解了模式的意图之后,再结合项目的实际场景才会知道。。模式的社区一直在发展。GoF在1995年提出了23种设计模式,但模式不仅仅局限于这23种,后面增加到了24种。在这20多年的时间里,也许有更多的模式已经被人发现并总结了出来,比如一些JavaScript 图书中会提到模块模式、沙箱模式等。这些“模式”能否被世人公认并流传下来,还有待时间验证。观察者模式(Observer Pattern)观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。观察者模式有一个别名叫“发布-订阅模式”,或者说是“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐个通知订阅者。我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是上面文章开头所说的“一对多”的依赖关系。发布订阅模式(Pub-Sub Pattern)其实24种基本的设计模式中并没有发布订阅模式,上面也说了,他只是观察者模式的一个别称。但是经过时间的沉淀,似乎他已经强大了起来,已经独立于观察者模式,成为另外一种不同的设计模式。在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。举一个例子,你在微博上关注了A,同时其他很多人也关注了A,那么当A发布动态的时候,微博就会为你们推送这条动态。A就是发布者,你是订阅者,微博就是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的(你的关注,A的发布动态)。观察者模式和发布订阅模式有什么区别?我们先来看下这两个模式的实现结构:观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。 发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。我们再来看下这两个模式的代码案例:(猎人发布与订阅任务)观察者模式: //有一家猎人工会,其中每个猎人都具有发布任务(publish),订阅任务(subscribe)的功能 //他们都有一个订阅列表来记录谁订阅了自己 //定义一个猎人类 //包括姓名,级别,订阅列表 function Hunter(name, level){ this.name = name this.level = level this.list = [] } Hunter.prototype.publish = function (money){ console.log(this.level + ‘猎人’ + this.name + ‘寻求帮助’) this.list.forEach(function(item, index){ item(money) }) } Hunter.prototype.subscribe = function (targrt, fn){ console.log(this.level + ‘猎人’ + this.name + ‘订阅了’ + targrt.name) targrt.list.push(fn) } //猎人工会走来了几个猎人 let hunterMing = new Hunter(‘小明’, ‘黄金’) let hunterJin = new Hunter(‘小金’, ‘白银’) let hunterZhang = new Hunter(‘小张’, ‘黄金’) let hunterPeter = new Hunter(‘Peter’, ‘青铜’) //Peter等级较低,可能需要帮助,所以小明,小金,小张都订阅了Peter hunterMing.subscribe(hunterPeter, function(money){ console.log(‘小明表示:’ + (money > 200 ? ’’ : ‘暂时很忙,不能’) + ‘给予帮助’) }) hunterJin.subscribe(hunterPeter, function(){ console.log(‘小金表示:给予帮助’) }) hunterZhang.subscribe(hunterPeter, function(){ console.log(‘小金表示:给予帮助’) }) //Peter遇到困难,赏金198寻求帮助 hunterPeter.publish(198) //猎人们(观察者)关联他们感兴趣的猎人(目标对象),如Peter,当Peter有困难时,会自动通知给他们(观察者)发布订阅模式: //定义一家猎人工会 //主要功能包括任务发布大厅(topics),以及订阅任务(subscribe),发布任务(publish) let HunterUnion = { type: ‘hunt’, topics: Object.create(null), subscribe: function (topic, fn){ if(!this.topics[topic]){ this.topics[topic] = []; } this.topics[topic].push(fn); }, publish: function (topic, money){ if(!this.topics[topic]) return; for(let fn of this.topics[topic]){ fn(money) } } } //定义一个猎人类 //包括姓名,级别 function Hunter(name, level){ this.name = name this.level = level } //猎人可在猎人工会发布订阅任务 Hunter.prototype.subscribe = function (topic, fn){ console.log(this.level + ‘猎人’ + this.name + ‘订阅了狩猎’ + topic + ‘的任务’) HunterUnion.subscribe(topic, fn) } Hunter.prototype.publish = function (topic, money){ console.log(this.level + ‘猎人’ + this.name + ‘发布了狩猎’ + topic + ‘的任务’) HunterUnion.publish(topic, money) } //猎人工会走来了几个猎人 let hunterMing = new Hunter(‘小明’, ‘黄金’) let hunterJin = new Hunter(‘小金’, ‘白银’) let hunterZhang = new Hunter(‘小张’, ‘黄金’) let hunterPeter = new Hunter(‘Peter’, ‘青铜’) //小明,小金,小张分别订阅了狩猎tiger的任务 hunterMing.subscribe(’tiger’, function(money){ console.log(‘小明表示:’ + (money > 200 ? ’’ : ‘不’) + ‘接取任务’) }) hunterJin.subscribe(’tiger’, function(money){ console.log(‘小金表示:接取任务’) }) hunterZhang.subscribe(’tiger’, function(money){ console.log(‘小张表示:接取任务’) }) //Peter订阅了狩猎sheep的任务 hunterPeter.subscribe(‘sheep’, function(money){ console.log(‘Peter表示:接取任务’) }) //Peter发布了狩猎tiger的任务 hunterPeter.publish(’tiger’, 198) //猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。观察者模式和发布订阅模式最大的区别就是发布订阅模式有个事件调度中心。观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,这种处理方式比较直接粗暴,但是会造成代码的冗余。而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰,消除了发布者和订阅者之间的依赖。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。观察者模式是不是发布订阅模式网上关于这个问题的回答,出现了两极分化,有认为发布订阅模式就是观察者模式的,也有认为观察者模式和发布订阅模式是真不一样的。其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构。如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。不过,不管他们是不是同一个设计模式,他们的实现方式确实有差别,我们在使用的时候应该根据场景来判断选择哪个。 ...

March 29, 2019 · 2 min · jiezi