我们前不久写过发布订阅模式:

// 发布者 Publisher
class Pub {
constructor() {
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
publish(dep) {
this.deps.forEach(item => item === dep && item.notify());
}
}
// 订阅者 Subscriber
class Sub {
constructor(val) {
this.val = val;
}

update(callback) {
callback(this.val)
}
}

// 调度中心
class Dep {
constructor(callback) { // 核心是这个 callback 函数;
this.subs = [];
this.callback = callback;
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach(item => item.update(this.callback));
}
}

let pub = new Pub() // 实例化一个发布者

// 实例化一个调度中心,传入一个用于处理数据的函数;
const dep1 = new Dep((data) =>
console.log('我是调度中心,我先把消息处理一下,然后发给 ===》》》', data))

let sub1 = new Sub("订阅者1") // 实例化订阅者1
let sub2 = new Sub("订阅者2") // 实例化订阅者2

pub.addDep(dep) // 发布者绑定调度中心

dep.addSub(sub1) // 调度中心添加订阅者1
dep.addSub(sub2) // 调度中心添加订阅者2

pub.publish(dep) // 发布者把消息推给调度者

// 我是调度中心,我先把消息处理下先 订阅者1
// 我是调度中心,我先把消息处理下先 订阅者2

但是这样看,似乎有点太复杂了:

  • 发布者需要有两个方法,绑定调度者 Dep,把消息推知给调度者;
  • 调度者也有两个方法,绑定订阅者 Sub,把消息推送给订阅者;
  • 订阅者有一个方法,执行函数;

这里面最重要的是有一个回调函数,作为调度中心的入参,会传给 Sub 执行;

于是,本篇带来 简化 了的思路进行理解:

比方说天气预报这个场景:气象站是需要发布信息的;建筑工地、船舶行业、普通游客是需要这些信息的;

如果我们直接强绑定这个通知关系,即:

function weatherWarning(weatherStatus){
if(weatherStatus==='warning'){ // 糟糕的天气
buildingsite.stopwork() // 工地停工
ships.mooring() // 船舶停航
tourists.canceltrip() // 旅游取消
}
}

weatherWarning("warning") // 发布坏天气通知

简化理解:发布订阅_观察者模式

​图片来源​

这样做,有无毛病?

还得是它俩:有毛病!违背开闭原则、违背单一职责原则;

违背开闭原则:上例中,如果有新的群体需要获取天气信息,要不断修改 weatherWarning 函数;

违背单一职责原则:上例中,任何一个群体代码执行错误,都会影响 weatherWarning 函数体代码的向下执行;

所以,还得改,于是引入:调度中心 Dep,这里叫 EventEmit

简化理解:发布订阅_发布订阅_02

​图片来源​

由调度中心来绑定需要信息的群体,即绑定订阅器,然后由调度中心发布信息给订阅者;

const EventEmit = function() { // 调度中心
this.events = {};
this.on = function(name, cb) { // 绑定订阅器
if (this.events[name]) {
this.events[name].push(cb); // 支持同一个订阅器执行多个事情
} else {
this.events[name] = [cb];
}
};
this.trigger = function(name, ...arg) { // 发送消息
if (this.events[name]) {
this.events[name].forEach(eventListener => {
eventListener(...arg);
});
}
};
};
let weatherEvent = new EventEmit() // 实例化一个调度中心

weatherEvent.on('warning', function () { // 绑定发布通知的关系
// buildingsite.stopwork()
console.log('buildingsite.stopwork()')
})

weatherEvent.on('warning', function () { // 绑定发布通知的关系
// ships.mooring()
console.log('ships.mooring()')
})

weatherEvent.on('warning', function () { // 绑定发布通知的关系
// tourists.canceltrip()
console.log('tourists.canceltrip()')
})

weatherEvent.trigger('warning') // 发布消息

当项目中存在一对多的依赖,且每个模块相对独立,可以考虑使用发布订阅模式来重构代码,即由调度中心来绑定、通知。

有工友可能疑问:这个怎么和之前说的【观察者模式】长得那么像?

class Subject{// 被观察者
constructor(){
this.observers=[]
}
add(observer){
this.observers.push(observer)
}
notify(weatherStatus){
this.observers.forEach(i=>i(weatherStatus))
}
}

let sub = new Subject()

sub.add((reason)=>{
// buildingsite.stopwork()
console.log('工地停工,因为天气:',reason)
})
sub.add((reason)=>{
// ships.stopwork()
console.log('船舶停航,因为天气:',reason)
})
sub.add((reason)=>{
// tourists.canceltrip()
console.log('旅游取消,因为天气:',reason)
})

sub.notify("warning") // sub 发布消息

// 工地停工,因为天气: warning
// 船舶停航,因为天气: warning
// 旅游取消,因为天气: warning

没错,我们可以再简化理解:观察者模式是发布订阅模式的一部分,如果你把被观察者视作调度中心的话呢,这就是发布订阅模式,如果你把订阅中心视作被观察者,那就是观察者模式;两者是可以互相转化的。

观察者模式:A 推给 ob1、ob2、ob3,一对多;

发布订阅模式: A 推给 Dep ,Dep 再推给 ob1、ob2、ob3,一对一,再对多;

发布订阅模式应该是我们前端开发者最常用的设计模式:

element.addEventListener('click', function(){ 
//...
})

OK,以上便是本篇分享。点赞关注评论,为好文助力👍

我是掘金安东尼 🤠 100 万阅读量人气前端技术博主 💥 INFP 写作人格坚持 1000 日更文 ✍ 关注我,陪你一起度过漫长编程岁月 🌏