观察者模式 vs 发布-订阅模式

147 阅读5分钟

什么是观察者模式(Observer)?

观察者模式与发布订阅相比,耦合度更高,观察者模式中只有两个主体 目标对象 【Subject】和观察者【Observer】

观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变的时候通知他们。 -- 维基百科

代码实现

1. 声明观察者Observer

class Observer {
  constructor (name) {
    this.name = name
  }

  // 观察者默认提供一个update方法,用于更新数据(业务逻辑代码)
  update (info, ...args) {
    console.log(`hello, ${this.name}, 告诉你一件事:${info}`)
  }
}

2. 声明目标对象Subject

class Subject {
  constructor () {
    this.observerList = []
  }

  addObserver (observer) {
    this.observerList.push(observer)
  }

  notify () {
    this.observerList.forEach(observer => observer.update(...arguments))
  }
}

整体代码实现

class Observer {
  constructor (name) {
    this.name = name
  }

  update (info, ...args) {
    console.log(`hello, ${this.name}, 告诉你一件事:${info}`)
  }
}

class Subject {
  constructor () {
    this.observerList = []
  }

  addObserver (observer) {
    this.observerList.push(observer)
  }

  notify () {
    this.observerList.forEach(observer => observer.update(...arguments))
  }
}

const Jay = new Observer('jay')
const James = new Observer('james')
const PublicSub = new Subject()

PublicSub.addObserver(Jay)
PublicSub.addObserver(James)


PublicSub.notify('海贼王更新了')
PublicSub.notify('上海复工了')

/**
 * 打印结果:
 * hello, jay, 告诉你一件事:海贼王更新了
 * hello, james, 告诉你一件事:海贼王更新了
 * hello, jay, 告诉你一件事:上海复工了
 * hello, james, 告诉你一件事:上海复工了
 */

观察者模式当对象更新时,会依次通知所有的观察者。

什么是发布订阅模式(Publisher & Subscriber)?

参考书籍-JavaScript设计模式与开发实践

举个例子

小明、小红都看上了某楼盘的房子,但是目前没有房源,小明、小红没有办法每天去售楼处询问是否有房源,于是,小明、小红将手机号留给了房产销售,某一天有房源了,销售依次给小明、小红打电话通知他们有房源了。

这种订阅房源信息的方式,就是发布-订阅模式。

注意:

  1. 以上的方式售楼处与购房者解耦,不再强耦合在一起
  2. 当有新的购房者出现,只需要把手机号留给售楼处(订阅)就行,售楼处并不关心购房者是男是女,还是一只猴子
  3. 售楼处的变化也不会影响购房者,只要售楼处记得通知购房者有新房源即可(发布)

释义

发布-订阅模式又叫观察者模式 它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。

特点:

  1. 发布-订阅模式广泛用于异步编程中,可以替代回调函数的方案
  2. 发布-订阅模式取代对象之间硬编码的通知机制,一个对象不再显式调用另一个对象的api。发布者、订阅者解耦,各自的变化并不会影响对方。

注意:多个订阅者订阅多个事件

代码实现

1. 订阅事件

class Pub {
  constructor () {
    /**
     * events[key] = []
     * 每个类型对应的订阅事件维护在数组中
     */
    this.events = {}
  }

  // 订阅
  listen (key, fn) {
    if (!this.events[key]) {
      this.events[key] = []
    }
    this.events[key].push(fn) // 新增订阅事件
  }
}

2. 发布事件

// 发布
trigger (key, ...args) {
  const fns = this.events[key]

  if (!fns || fns.length === 0) { // 无订阅事件
    return false
  }

  for (let i = 0, fn = null; fn = fns[i++];) {
    fn.apply(this, args)
  }
}

3. 取消订阅

// 取消订阅
  removeListener (key, fn) {
    if (this.events[key]) {
      const fnIndex = this.events[key].findIndex(e => e === fn)
      if (fnIndex !== -1) {
        this.events[key].splice(fnIndex, 1)
      }
    }

    if (this.events[key].length === 0) {
      delete this.events[key]
    }
  }

  // 取消所有订阅
  removeAllListener (key) {
    if (this.events[key]) {
      delete this.events[key]
    }
  }

整体代码实现

class Pub {
  constructor () {
    this.events = {}
  }

  // 订阅
  listen (key, fn) {
    if (!this.events[key]) {
      this.events[key] = []
    }
    this.events[key].push(fn) // 新增订阅事件
  }

  // 发布
  trigger (key, ...args) {
    const fns = this.events[key]

    if (!fns || fns.length === 0) { // 无订阅事件
      return false
    }

    for (let i = 0, fn = null; fn = fns[i++];) {
      fn.apply(this, args)
    }
  }

  // 取消订阅
  removeListener (key, fn) {
    if (this.events[key]) {
      const fnIndex = this.events[key].findIndex(e => e === fn)
      if (fnIndex !== -1) {
        this.events[key].splice(fnIndex, 1)
      }
    }

    if (this.events[key].length === 0) {
      delete this.events[key]
    }
  }

  // 取消所有订阅
  removeAllListener (key) {
    if (this.events[key]) {
      delete this.events[key]
    }
  }
}

const publish = new Pub()

// jack和jay都订阅88w的房子
const Jack_sub_88 = city => {
  console.log('新出88w楼盘,位置在' + city + ',通知jack')
}
const Jay_sub_88 = city => {
  console.log('新出88w楼盘,位置在' + city + ',通知jay')
}
publish.listen('price88', Jack_sub_88)
publish.listen('price88', Jay_sub_88)

// james订阅100w的房子
const James_sub_100 = city => {
  console.log('新出100w楼盘,位置在' + city + ',通知james')
}
publish.listen('price100', James_sub_100)

publish.trigger('price88', '南京') // 南京发布了一套88w的房子, 通知jack和jay
publish.trigger('price88', '苏州') // 苏州发布了一套88w的房子,通知jack和jay
publish.trigger('price100', '上海') // 上海发布了一套100w的房子,通知james

publish.removeListener('price88', Jack_sub_88) // jack已购房,取消订阅
publish.trigger('price88', '南通') // 南通发布了一套88w的房子,通知jay

观察者模式与发布订阅模式有什么区别?

设计模式观察者模式发布订阅模式
主体Object观察者、Subject目标对象Publisher发布者、Event Channel事件中心、Subscribe订阅者
主体关系Subject中通过observerList记录ObServerPublisher和Subscribe不想不知道对方,通过中介联系
优点角色明确,Subject和Object要遵循约定的成员方法松散耦合,灵活度高,通常应用在异步编程中
缺点紧耦合当事件类型变多时,会增加维护成本
使用案例双向数据绑定事件总线EventBus、vue.$emits等

使用案例

观察者模式

vue数据双向绑定原理(数据劫持)

数据劫持简单示例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue-observer-simple</title>
</head>
<body>
  <div>
    <input type="text" id="text">
    <p id="show"></p>
  </div>
</body>
<script>
  const observer = {}
  Object.defineProperty(observer, 'text', {
    get: function () {
      return observer
    },
    set: function (newVal) {
      document.getElementById('text').value = newVal
      document.getElementById('show').innerHTML = newVal
    }
  })
  document.addEventListener('keyup', function (e) {
    observer.text = e.target.value
  })
</script>
</html>

[警告]注意:vue中数据双向绑定的数据劫持部分(Observer)是观察者模式,但是整个双向绑定是发布-订阅模式

发布订阅模式

node.js中的EventEmitter

api 清单

  • emitter.addListener(eventName, listener) / emitter.on(eventName, listener)
  • emitter.off(eventName, listener) / emitter.removeListener(eventName, listener)
  • emitter.emit(eventName, [...args])
  • emitter.once(eventName, listener)
  • emitter.removeAllListeners([eventName])

额外的api扩展:

  • emitter.getMaxListeners()
  • emitter.setMaxListeners(n) -- 设置最大绑定数,默认超过10个会警告
  • emitter.prependListener(eventName, listener) / emitter.prependOnceListener(eventName, listener) -- 向前添加监听事件

调整发布订阅示例代码

  • emitter.addListener(eventName, listener)
  • emitter.emit(eventName, [...args])
  • emitter.removeListener(eventName, listener)
  • emitter.removeAllListeners([eventName])
class EventEmitter {
  constructor () {
    // 存放订阅事件
    this._events = Object.create(null)
  }

  // 订阅事件
  addListener (eventName, listener) {
    if (!this._events[eventName]) {
      this._events[eventName] = []
    }
    this._events[eventName].push(listener) // 新增订阅事件
  }

  // 发布
  emit (eventName, ...args) {
    const listeners = this._events[eventName]

    if (!listeners || listeners.length === 0) { // 无订阅事件
      return false
    }

    for (let i = 0, listener = null; listener = listeners[i++];) {
      listener.apply(this, args)
    }
  }

  // 取消订阅
  removeListener (eventName, listener) {
    if (this._events[eventName]) {
      const listenerIndex = this._events[eventName].findIndex(e => e === listener)
      if (listenerIndex !== -1) {
        this._events[eventName].splice(listenerIndex, 1)
      }
    }

    if (this._events[eventName].length === 0) {
      delete this._events[eventName]
    }
  }

  // 取消所有订阅
  removeAllListener (eventName) {
    if (this._events[eventName]) {
      delete this._events[eventName]
    }
  }
}

const testEvent = new EventEmitter()

const event_1 = (name) => {
  console.log(`触发了事件${name}`)
}

testEvent.addListener('event_1', event_1)

testEvent.emit('event_1', '事件1')

完成emitter.on(eventName, listener)和emitter.off(eventName, listener)

emitter.on 即 emitter.addListener,是emitter.addListener的别名简写

同样的

emitter.off 即 emitter.removeListener,是emitter.addListener的别名简写

  • emitter.on(eventName, listener)

node.js源码:lib/events.js

/**
 * Adds a listener to the event emitter.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.addListener = function addListener(type, listener) {
  return _addListener(this, type, listener, false);
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;
  • emitter.off(eventName, listener)

node.js源码:lib/events.js

/**
 * Removes the specified `listener` from the listeners array.
 * @param {string | symbol} type
 * @param {Function} listener
 * @returns {EventEmitter}
 */
EventEmitter.prototype.removeListener =
    function removeListener(type, listener) {
      // ...

      return this;
    };

EventEmitter.prototype.off = EventEmitter.prototype.removeListener;

代码实现

class EventEmitter {
  constructor () {
    // 存放订阅事件
    this._events = Object.create(null)
  }

  // 订阅事件
  addListener (eventName, listener) {
    if (!this._events[eventName]) {
      this._events[eventName] = []
    }
    this._events[eventName].push(listener) // 新增订阅事件
  }

  // 订阅事件 -- addListener的简写
  on (eventName, listener) {
    this.addListener(eventName, listener)
  }

  // 发布
  emit (eventName, ...args) {
    // ...
  }

  // 取消订阅
  removeListener (eventName, listener) {
    // ...
  }
  
  // 取消订阅 - removeListener的简写
  off (eventName, listener) {
    this.removeListener(eventName, listener)
  }

  // 取消所有订阅
  removeAllListener (eventName) {
    // ...
  }
}

完成emitter.once(eventName, listener)

  • 将listener二次包装
  • 先绑定,调用listener后删除

once相关代码:

once (eventName, listener) {
    function _wrap () {
      listener(...arguments)
      this.removeListener(eventName, _wrap)
    }

    _wrap.listener = listener

    this.on(eventName, _wrap)
  }

全量代码:

class EventEmitter {
  constructor () {
    // 存放订阅事件
    this._events = Object.create(null)
  }

  // 订阅事件
  addListener (eventName, listener) {
    if (!this._events[eventName]) {
      this._events[eventName] = []
    }
    this._events[eventName].push(listener) // 新增订阅事件
  }

  // 订阅事件 -- addListener的简写
  on (eventName, listener) {
    this.addListener(eventName, listener)
  }

  once (eventName, listener) {
    function _wrap () {
      listener(...arguments)
      this.removeListener(eventName, _wrap)
    }

    _wrap.listener = listener

    this.on(eventName, _wrap)
  }

  // 发布
  emit (eventName, ...args) {
    const listeners = this._events[eventName]

    if (!listeners || listeners.length === 0) { // 无订阅事件
      return false
    }

    for (let i = 0, listener = null; listener = listeners[i++];) {
      listener.apply(this, args)
    }
  }

  // 取消订阅
  removeListener (eventName, listener) {
    if (this._events[eventName]) {
      const listenerIndex = this._events[eventName].findIndex(e => e === listener)
      if (listenerIndex !== -1) {
        this._events[eventName].splice(listenerIndex, 1)
      }
    }

    if (this._events[eventName].length === 0) {
      delete this._events[eventName]
    }
  }

  // 取消订阅 -- removeListener的简写
  off (eventName, listener) {
    this.removeListener(eventName, listener)
  }

  // 取消所有订阅
  removeAllListener (eventName) {
    if (this._events[eventName]) {
      delete this._events[eventName]
    }
  }
}

const testEvent = new EventEmitter()

const event_1 = (name) => {
  console.log(`触发了事件${name}`)
}

testEvent.once('event_1', event_1)

testEvent.emit('event_1', '事件1触发第一次')
testEvent.emit('event_1', '事件1触发第二次')

完成向前添加监听事件

  • emitter.prependListener(eventName, listener)
  • emitter.prependOnceListener(eventName, listener)

利用flag进行判断处理

if (flag) {
  this._events[eventName].unshift(listener);
} else {
  this._events[eventName].push(listener);
}

相关代码调整:

    // 订阅事件
  addListener (eventName, listener, flag) {
    if (!this._events[eventName]) {
      this._events[eventName] = []
    }
    if (flag) {
      this._events[eventName].unshift(listener) // 向前新增订阅事件
    } else {
      this._events[eventName].push(listener) // 新增订阅事件
    }
  }

  // 订阅事件 -- addListener的简写
  on (eventName, listener, flag) {
    this.addListener(eventName, listener, flag)
  }

  // 订阅事件 -- 只订阅一次
  once (eventName, listener, flag) {
    function _wrap () {
      listener(...arguments)
      this.removeListener(eventName, _wrap)
    }

    _wrap.listener = listener

    this.on(eventName, _wrap, flag)
  }

  // 向前添加订阅事件
  prependListener (eventName, listener) {
    this.on(eventName, listener, true)
  }

  // 向前添加订阅事件 -- 只订阅一次
  prependOnceListener (eventName, listener) {
    this.once(eventName, listener, true)
  }

添加最大监听数控制

  • emitter.getMaxListeners()
  • emitter.setMaxListeners(n) -- 设置最大绑定数,默认超过10个会警告
class EventEmitter {
  constructor () {
    // 存放订阅事件
    this._events = Object.create(null)
    // 最大监听数
    this.defaultMaxListeners = 10
    this._count = this.defaultMaxListeners
  }

  getMaxListeners () {
    return this._count ? this._count : this.defaultMaxListeners
  }

  setMaxListeners (num) {
    this._count = num
  }

  // 订阅事件
  addListener (eventName, listener, flag) {
    if (!this._events[eventName]) {
      this._events[eventName] = []
    }
    if (flag) {
      this._events[eventName].unshift(listener) // 向前新增订阅事件
    } else {
      this._events[eventName].push(listener) // 新增订阅事件
    }

    if (this._events[eventName].length >= this.getMaxListeners()) {
      console.warn('添加的监听事件已超过最大监听数')
    }
  }
  
  // ...
}

EventEmitter全量代码实现

class EventEmitter {
  constructor () {
    // 存放订阅事件
    this._events = Object.create(null)
    // 最大监听数
    this.defaultMaxListeners = 10
    this._count = this.defaultMaxListeners
  }

  getMaxListeners () {
    return this._count ? this._count : this.defaultMaxListeners
  }

  setMaxListeners (num) {
    this._count = num
  }

  // 订阅事件
  addListener (eventName, listener, flag) {
    if (!this._events[eventName]) {
      this._events[eventName] = []
    }
    if (flag) {
      this._events[eventName].unshift(listener) // 向前新增订阅事件
    } else {
      this._events[eventName].push(listener) // 新增订阅事件
    }

    if (this._events[eventName].length >= this.getMaxListeners()) {
      console.warn('添加的监听事件已超过最大监听数')
    }
  }

  // 订阅事件 -- addListener的简写
  on (eventName, listener, flag) {
    this.addListener(eventName, listener, flag)
  }

  // 订阅事件 -- 只订阅一次
  once (eventName, listener, flag) {
    function _wrap () {
      listener(...arguments)
      this.removeListener(eventName, _wrap)
    }

    _wrap.listener = listener

    this.on(eventName, _wrap, flag)
  }

  // 向前添加订阅事件
  prependListener (eventName, listener) {
    this.on(eventName, listener, true)
  }

  // 向前添加订阅事件 -- 只订阅一次
  prependOnceListener (eventName, listener) {
    this.once(eventName, listener, true)
  }

  // 发布
  emit (eventName, ...args) {
    const listeners = this._events[eventName]

    if (!listeners || listeners.length === 0) { // 无订阅事件
      return false
    }

    for (let i = 0, listener = null; listener = listeners[i++];) {
      listener.apply(this, args)
    }
  }

  // 取消订阅
  removeListener (eventName, listener) {
    if (this._events[eventName]) {
      const listenerIndex = this._events[eventName].findIndex(e => e === listener)
      if (listenerIndex !== -1) {
        this._events[eventName].splice(listenerIndex, 1)
      }
    }

    if (this._events[eventName].length === 0) {
      delete this._events[eventName]
    }
  }

  // 取消订阅 -- removeListener的简写
  off (eventName, listener) {
    this.removeListener(eventName, listener)
  }

  // 取消所有订阅
  removeAllListener (eventName) {
    if (this._events[eventName]) {
      delete this._events[eventName]
    }
  }
}

const testEvent = new EventEmitter()

const event_1 = (name) => {
  console.log(`触发了事件${name}`)
}

testEvent.once('event_1', event_1)

testEvent.emit('event_1', '事件1触发第一次')
testEvent.emit('event_1', '事件1触发第二次')