发布-订阅模式

123 阅读2分钟

定义:

基于一个事件(主题)通道,希望接收通知的对象 Subscriber 通过自定义事件订阅主题,被激活事件的对象 Publisher 通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象(发布-订阅者模式只是观察者模式的别名,随着时间推移,在观察者基础上,发展成一种不同的设计模式,功能更强大)

场景:

1.当一个对象的状态变化需要通知其他多个对象时,可以使用发布订阅模式来实现松耦合的通信

2.当一个事件或消息需要广泛传播或分发给多个接收者时,可以使用发布订阅模式来实现高效的消息分发

3.当一个系统需要支持异步处理或批量处理时,可以使用发布订阅模式来实现事件的延迟触发或批量触发

优点(比较与观察者模式):

1.可以一对多也可以多对多

2.主体之间松耦合

3.可以并行操作

4.多用于异步操作

缺点:

1.当消息事件中间件采用定时发布通知时,订阅发布模式无法确认所有的订阅者都收到了消息

2.当负载激增,请求订阅的订阅者数量增加,每个订阅者接收到通知的速度将会变慢

与观察者模式区别

1.观察者模式内部维护了观察者,知道有哪些观察者的存在,而发布/订阅模式则省略了这一步骤,直接维护订阅者的事件机制,不用管谁在订阅

2.发布/订阅模式相比观察者模式多了一个中间媒介,有了这个中间媒介之后,发布者和订阅者的关联就更加的松耦合

3.观察者模式通常用于同步的场景, 而发布/订阅模式大多用于异步的场景,如消息队列

具体实现

class Store {
  constructor () {
    this.repertory = {}
  }
  $on (name, fn) {
    let that = this
    if (Array.isArray(name)) {
      name.forEach(nme => {
        that.$on(nme, fn)
      })
    } else {
      (that.repertory[name] || (that.repertory[name] = [])).push(fn)
    }
  }
  $emit (name) {
    let that = this
    let args = Array.from(arguments)
    let cbs = this.repertory[name]
    if (!cbs || cbs.length == 0) {
      throw Error(`xxx`)
    }
    cbs.forEach(cb => {
      cb.call(that, ...args)
    })
    return that
  }
  $off (name, fn) {
    let that = this
    if (!arguments.length || arguments.length == 0) {
      that.repertory = Object.create(null)
      return that
    }
    if (Array.isArray(name)) {
      name.forEach(nme => {
        that.$off(nme, fn)
      })
      return that
    }
    let cbs = that.repertory[name]
    if (!cbs) {
      throw Error('xxx')
    }
    if (!fn) {
      that.repertory[name] = null
      return that
    }
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb == fn || cb.fn == fn) {
        cbs.splice(i, 1)
        break
      }
    }
  }
  $once (name, fn) {
    let that = this
    function on () {
      that.$off(name, on)
      fn.apply(that, arguments)
    }
    on.fn = fn
    that.$on(name, on)
  }
}