观察者模式和发布订阅模式的概念在项目开发中很常见 ,这里记录一下自己的理解,详解一下两者的区别的并分别用代码实现来直观体现两者区别,便于日后温故知新
观察者模式
观察者模式别名也叫发布-订阅模式,但是发布订阅模式其实只是观察者模式中的一种具体的实现方式
通常我们所说的观察者和发布订阅模式的区别,其实只是把观察者模式的一种原始的实现方式和现在的流行的发布订阅实现方式对比)。
好了,现在先在我们来理解一下原来的观察者模式是如何实现的,试讲订阅者和发布者直接关联的,将多个订阅者直接绑定到发布者身上,然后当发布者触发事件的时候吗通知各个订阅者, 是一对多的关系。
这个我们可以举个例子,这个实现方式就类似于原始的劳动市场,应聘者是订阅者到劳动市场把简历投递到一个个公司的邮箱里,而公司相当与发布者,当有一个hc时,公司就开始一个个联系各个应聘者,完成一次发布。
图解:(google找的)
代码:
//观察者模式
//招聘者
class Subject {
constructor() {
this.observers = [] //观察者列表(应聘者列表)
}
addObserver(obj) { //添加观察者(接受简历)
this.observers.push(obj)
}
notify(msg) { //通知观察者 (通知应聘者来应聘)
this.observers.forEach((e)=> {
e.receiverNotice(msg)
})
}
}
//应聘者
class Observer {
constructor(params) {
this.name = params.name
}
receiverNotice(msg) {
//TODO 收到消息 赶紧去面试啊!!!
}
}
const ZhangSan = new Observer({name: '张三'})
const LiSi = new Observer({name: '李四'})
const company = new Subject()
//绑定观察(投递简历)
company.addObserver(ZhangSan)
company.addObserver(LiSi)
//发布时间 (通知)
company.notify('快来面试啊!')
订阅发布模式
上文已经说了订阅发布模式其实只是观察者模式的一种是实现方式, 是一种升级但是现在通常被当成一种单独的设计模式。
我们可以将订阅发布模式抽象理解成现在的劳动市场,公司和应聘者不直接接触,而由劳动市场做为事件中心做转发,应聘者向劳动市场登记自己的信息和技能,等到有公司向劳动市场发布岗位的时候, 由劳动市场来一个个通知登记了的应聘者。这与上面的观察者模式最大的区别就在于加一个事件中心(劳动中介)。
这样公司就不需要在管理订阅者,完全交由事件中心处理。 订阅者和发布者完全解耦,发布者不需要关注怎么绑定订阅者,和如何发布给订阅者, 订阅时不需要双方都在场直接绑定 。这样能大大让代码逻辑更为清晰。同时可以将事件进行集中管理,可以进行统一的逻辑操作。
图解:(来源google)
代码
//event.js
class Events {
//事件池
pool = {}
on(eventKey, fn) {
if (!eventKey) return;
if (!fn || typeof fn !== 'function') {
return;
}
let eventPool = this.pool[eventKey] || [];
let flag = false;
if (eventPool.length) {
// 相同事件重复绑定
for (let i = 0; i < eventPool.length; i++) {
if (eventPool[i] === fn) {
flag = true;
break;
}
}
!flag ? eventPool.push(fn) : '';
} else {
eventPool.push(fn)
}
this.pool[eventKey] = eventPool;
}
//发布时间
emit(eventKey) {
let p = Promise.resolve();
if (!eventKey) return;
let eventPool = this.pool[eventKey] || [], args;
if (eventPool.length) {
args = [].splice.call(arguments, 1);
eventPool.map(item => {
p = p.then(() => {
return itemFunc.apply(null, args);
}).catch(e => {
console.log(e)/
})
})
}
}
//解除事件绑定
off(eventKey, fn) {
let pool = this.pool;
// 解除所有事件
if (!eventKey) {
this.pool = {}
return;
}
// 解除所有eventKey事件
if (!fn || typeof fn !== 'function') {
delete pool[eventKey];
return;
}
if (!pool[eventKey]) {
return;
}
var mindex = pool[eventKey].findIndex((itemFunc => itemFunc === fn));
~mindex ? pool[eventkey].splice(mindex, 1) : '';
this.pool = pool;
}
}
const eventBus = new Events()
export default eventBus;
---------------------
// employee.js 注册事件(登记信息)
import eventBus form "./event.js"
eventBus.on("应聘",()=> {
//TODO
})
---------------------
//compony.js 触发时间(发布一个岗位)
import eventBus form "./event.js"
eventBus.emit("应聘")
上面就是一个发布订阅设计模式实现的事件中心类,这个类可以直接在项目中使用,
首先这里需要知道的是在各个文件中导入eventBus时, 只会导出同一个对象,event.js文件只会执行一次 ,一个项目中每次import的只是同一个对象的指针。(具体看一下es6中import的导入方式)
所以事件在各个文件中共享。这样就可以保证多个文件进行事件交互。因为订阅事件的时候用的是箭头函数,所以可以在外部修改作用域内的参数。这是很常用的开发场景。 类似多层的组件嵌套就可以不需要一层一层传递参数而用事件来触发