发布订阅模式 代码设计模式
Publishers + Subscribers = Observer Pattern ?
_
所谓观察者模式,其实就是为了实现松耦合(loosely coupled)。
用《Head First设计模式》里的气象站为例子,每当气象测量数据有更新,changed()
方法就会被调用,于是我们可以在changed()
方法里面,更新气象仪器上的数据,比如温度、气压等等。
js原生的dom一级事件池,以及dom2事件池
但是这样写有个问题,就是如果以后我们想在changed()
方法被调用时,更新更多的信息,比如说湿度,那就要去修改changed()
方法的代码,这就是紧耦合的坏处。
怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。
**
观察者模式里面,changed()
方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了:
DOM 二级事件
window.addEventListener(‘scroll’, debounceTask)
// 8. 事件总线 | 发布订阅模式
class EventEmitter {
constructor() {
this.cache = {}
}
// 向事件池中去添加属性名为“name”订阅事件集合
// 严谨一点需要首先判断订阅事件内部都要是函数,且不能重名
on(name, fn) {
if (this.cache[name]) {
// 属性非空的情况下可以一定是数组
const repeat = this.cache[name].some(f => return f === fn)
// 数组去重同名事件不可以添加两次
!repeat ? this.cache[name].push(fn) : null
} else {
// 如果为空的话直接数组化
this.cache[name] = [fn]
}
}
// 事件池事件注销
off(name, fn) {
// 获取名为name的订阅事件集合,不为空
const tasks = this.cache[name]
if (tasks) {
// 寻找取消订阅事件的索引,找到对应的索引
const index = tasks.findIndex((f) => f === fn || f.callback === fn)
if (index >= 0) {
// tasks.splice(index, 1) 可能会导致数组塌陷
tasks[index] = null;
}
}
}
// 执行name订阅事件
emit(name) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
const tasks = this.cache[name].slice()
for (let i=0; i<tasks.length ; i++) {
if( typeof tasks[i] !== "function" ){
tasks.splice(i, 1)
i--
continue
}
tasks[i]();
}
}
}
emit(name, once = false) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
const tasks = this.cache[name].slice()
for (let i=0; i<tasks.length ; i++) {
if( typeof tasks[i] !== "function" ){
tasks.splice(i, 1)
i--
continue
}
tasks[i]();
}
if (once) {
delete this.cache[name]
}
}
}
}
// 测试
const eventBus = new EventEmitter()
const task1 = () => { console.log('task1'); }
const task2 = () => { console.log('task2'); }
eventBus.on('task', task1)
eventBus.on('task', task2)
setTimeout(() => {
eventBus.emit('task')
}, 1000)
// 内部可能有事件坍塌事件集合
从表面上看:
- 观察者模式里,只有两个角色 —— 观察者 + 被观察者
- 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker
往更深层次讲:
- 观察者和被观察者,是松耦合的关系
- 发布者和订阅者,则完全不存在耦合
从使用层面上讲:
- 观察者模式,多用于单个应用内部
- 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件
快递员上门送货,后来快递太多了,为了增加效率,分工更明确一点,现在多了个中间站,菜鸟驿站,快递员方便了,这是在规模起来以后自然而然的选择。
现在是人主动去拿快递,如果以后连这也嫌弃效率不高,怎么办?
再加一层,菜鸟驿站派出机器人送,