发布-订阅模式

124 阅读3分钟

1. 定义

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到状态改变的通知。

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

举个例子:

比如我们很喜欢看某个公众号号的文章,但是我们不知道什么时候发布新文章,要不定时的去翻阅;这时候,我们可以关注该公众号,当有文章推送时,会有消息及时通知我们文章更新了。

上面一个看似简单的操作,其实是一个典型的发布订阅模式,公众号属于发布者,用户属于订阅者;用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。

2. 实现思路

  • 创建一个对象

  • 在该对象上创建一个缓存列表(调度中心)

  • on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)

  • emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)

  • off 方法可以根据 event 值取消订阅(取消订阅)

  • once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)

代码示例:

const eventHub = { 
    map: { 
        // click: [f1 , f2] 
    }, 
    on: (name, fn)=>{ 
        eventHub.map[name] = eventHub.map[name] || [] 
        eventHub.map[name].push(fn) 
    }, 
    emit: (name, data)=>{ 
        const q = eventHub.map[name] 
        if(!q) return 
        q.map(f => f.call(null, data)) 
        return undefined 
    }, 
    off: (name, fn)=>{ 
        const q = eventHub.map[name] 
        if(!q){ return } 
        const index = q.indexOf(fn) 
        if(index < 0) { return } 
        q.splice(index, 1) 
    } 
} 

eventHub.on('click', console.log) 
eventHub.on('click', console.error) 

setTimeout(()=>{ 
    eventHub.emit('click', 'frank') 
},3000)

也可以用class实现:

class EventHub { 
    map = {} 
        on(name, fn) { 
        this.map[name] = this.map[name] || [] 
        this.map[name].push(fn) 
    } 
    emit(name, data) { 
        const fnList = this.map[name] || [] 
        fnList.forEach(fn => fn.call(undefined, data)) 
    } 
    off(name, fn) { 
        const fnList = this.map[name] || [] 
        const index = fnList.indexOf(fn) 
    if(index < 0) return 
        fnList.splice(index, 1) 
    } 
} 

// 使用 
const e = new EventHub() 
e.on('click', (name)=>{ 
    console.log('hi '+ name) 
}) 
e.on('click', (name)=>{ 
    console.log('hello '+ name) 
}) 
setTimeout(()=>{ 
    e.emit('click', 'frank') 
},3000)

3. 总结

优点:

  • 对象之间解耦
  • 异步编程中,可以更松耦合的代码编写

缺点:

  • 创建订阅者本身要消耗一定的时间和内存
  • 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护

4. 扩展(发布-订阅模式与观察者模式的区别)

image.png

观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

发布订阅模式:订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

差异

  • 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
  • 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
  • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

5. 参考链接

JavaScript 发布-订阅模式 - xiaoxiaobaibai