设计模式-JS中的观察者模式与发布订阅模式

背景

在上一篇文章《debounce与throttle》 中提到了 RxJS, RxJS 中有两个重要的概念: Observable(可被观察者) 和 Observer(观察者),RxJS 的 Observable 就是观察者模式和迭代器模式的组合。

在 javascript 中,观察者模式和发布订阅模式极为相似,因此本文将探讨这两种模式以及其应用。

观察者模式(Observer pattern)

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。 —— Graphic Design Patterns

观察者模式有两个对象:一个是观察者,另一个上面提到的目标对象可以称为是发布者,基本的流程为:

  1. 发布者维护一个观察者列表,可以添加或者删除观察者
  2. 当发布者发布通知时,遍历观察者列表,并调用观察者提供的方法

发布者的实现如下:

// 发布者
class Publisher {
  constructor() {
    // 维护一个观察者列表
    this.observers = []
    
  }    
  // 添加观察者
  addObserver(observer) {
    this.observers.push(observer)
  }
  // 移除观察者
  removeObserver(observer) {
    this.observers = this.observers.filter(item => item !== observer)
  }
  // 发布通知
  notify() {
    this.observers.forEach((item) => {
      // 执行观察者提供的方法,这里假设为 update
      item.update()
    })
  }
}

订阅者则比较简单,实现如下:

class Observer {
  constructor(name) {
    this.name = name
  }
  update() {
    // 更新状态
    console.log(`observer ${this.name} update`)
  }
}

最终:

const a = new Observer('a')
const b = new Observer('b')
const c = new Observer('c')

const publisher = new Publisher()
publisher.addObserver(a)
publisher.addObserver(b)
publisher.addObserver(c)
// 发布通知
publisher.notify()
// a b c

Vue 的响应式原理就是观察者模式的一个典型应用,关于 Vue 的响应式由于篇幅原因,将会另起一篇详细分析。

发布订阅模式(Pub-sub pattern)

发布订阅模式是从观察者模式发展而来,两者的区别在于:观察者模式中发布者与观察者是松耦合,而发布订阅则在发布者与订阅者中加入了事件中心实现发布者与观察者的完全解耦。

用发布订阅模式模拟事件绑定:

class EventCenter {
  constructor() {
    this.observers = {}
  }
  addListener(type, fn) {
    this.observers[type] = [...(this.observers[type] || []), fn]
  }
  removeListener(type, fn) {
    let listeners = this.observers[type];
    if (!listeners || !listeners.length) return;
    this.observers[type] = listeners.filter(v => v !== fn);
  }
  dispatchEvent(type) {
    let listeners = this.observers[type];
    if (!listeners || !listeners.length) return;
    listeners.forEach(fn => fn());        
  }
}

const evtCenter = new EventCenter()
function handleClick () {
  console.log('click')
}
// 订阅 click 事件
evtCenter.addListener('click', handleClick)
// 发布 click 事件
evtCenter.dispatchEvent('click')
// 取消订阅
evtCenter.removeListener('click', handleClick)

可以看到与观察者模式不同,在发布订阅模式中,我们的发布和订阅都是通过事件中心来进行调度的。