发布订阅者模式
定义
基于一个主题/事件通道,希望接收通知的对象通过自定义事件订阅主题,被激活事件的对象通过发布主题事件的方式被通知。
发布订阅者模式是一种对象间一对多的依赖关系(利用消息队列)。当一个对象的状态发生改变时,所有依赖于它的对象都得到状态改变的通知。
场景
小明、小红、小林、小赵打算报补习班,小明、小红报的英语补习班,小林、小赵报的数学补习班,但现在补习班还没开课。
补习班负责人说:你们可以先把手机号码留下,等要开课了我再给你们打电话。
于是英语补习班开课了,负责人就给小明和小红打电话了
数学补习班开课了,负责人就给小林和小赵打电话了
我们用发布订阅者模式去实现上述的场景,那么补习班负责人就是发布者,他发布补习班开课的通知给对应的人,小明、小红、小林、小赵我们是属于订阅者,我们订阅我们要去的补习班的开课通知,当我们订阅的课要开始了,发布者就会通知我们。
代码实现
思路
- 创建一个类。
- 在该类上创建一个缓存列表(调度中心)。
- 要有一个
on方法来把函数fn都加到缓存列表中,也就是订阅者注册时间到调度中心。 - 要有一个
emit方法取到event事件类型,根据event值去执行对应缓存列表中的函数,也就是发布者发布事件到调度中心,调度中心处理代码。 - 要有一个
off方法,根据event事件类型取消订阅。
代码
class Observer {
constructor() {
// 所有订阅的消息都存入到这个调度中心
this.subjects = {}
}
// 消息订阅
$on(type, fn) {
// 判断调度中心是否存在这个属性,不存在就初始化一个空数组
if (!this.subjects[type]) this.subjects[type] = []
// 向数组中添加传入的订阅回调
this.subjects[type].push(fn)
}
// 消息发布
$emit(type) {
// 判断调度中心是否存在此订阅,没有就结束
if (!this.subjects[type]) return
// 循环执行此订阅里的所有消息
this.subjects[type].forEach(item => item())
}
// 取消订阅
$off(type, fn) {
// 判断调度中心是否存在此订阅,没有就结束
if (!this.subjects[type]) return
// 判断是否传入具体fn, 若未穿入直接删除整个消息队列
if (!fn) this.subjects[type] = undefined
// 若有具体fn,删除订阅里的此方法
else {
this.subjects[type] = this.subjects[type].filter(item => item !== fn)
}
}
}
上面是一个我们实现的发布订阅者模式实例,我们现在把我们的场景带入看下:
let observer = new Observer()
let fn1= function () {
console.log('小明的手机号码:11345165432')
}
let fn2= function () {
console.log('小红的手机号码:12345165432')
}
let fn3= function () {
console.log('小赵的手机号码:12345665332')
}
let fn4= function () {
console.log('小林的手机号码:12345665412')
}
// 现在去订阅每个人的课程
observer.$on('English', fn1)
observer.$on('English', fn2)
observer.$on('maths', fn3)
observer.$on('maths', fn4)
// 这里就实现了 课程的订阅
// 比如当英语要开课的时候,负责人去通知上英语课的同学
observer.$emit('English')
// log: 小明的手机号码:11345165432
// log: 小红的手机号码:12345165432
// 这样就通知了要上英语课的同学
// 现在比如小赵有别的事情导致他不能去上数学补习班,那么我们取消小赵订阅的事件
observer.$off('maths',fn3 )
// 这样当通知数学要开课的时候就只会通知小林了,因为小赵有事取消了数学课的订阅
observer.$off('maths',fn3 )
// log: 小林的手机号码:12345665412
// 现在补习班突发情况导致英语没法开课了,那么负责人就取消要英语课上面所有的订阅了
// 我们上面所封装了这种情况,只需传入属性名字即可,这样就取消了这个属性上的所有消息
observer.$off('English')
总结
发布订阅者模式:
- 发布订阅模式中,发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,互不关心对方是谁。
- 松散耦合,灵活度高,常用作事件总线。
如果项目中存在多对一的依赖,并且每个模块都相对独立,那么可以考虑使用发布订阅模式来重构代码。而发布订阅模式是前端开发中最常用的设计模式,很多框架都有用到这一设计模式。
案例: Vue事件总线(EventBus)
Vue双向数据绑定也结合了这一思想
GitHub
https://github.com/zdongfeng/Pro-JavaScript-Design-Patterns