觀察者模式與釋出訂閱模式區別 - JS

時傾發表於2022-03-14

觀察者模式

當物件之間存在一對多的依賴關係時,當被觀察的物件狀態發生改變時,所有觀察它的物件都會收到通知,這就是觀察者模式。

基本思想

在觀察者模式中,只有兩種主體:目標物件 (Subject) 和 觀察者 (Observer)。
在觀察者模式中,Subject 物件擁有新增、刪除和通知一系列 Observer 的方法等,而 Observer 物件擁有 update 更新方法等。在 Subject 物件新增了一系列 Observer 物件之後,Subject 物件則維持著這一系列 Observer 物件,當有關狀態發生變更時 Subject 物件則會通知這一系列 Observer 物件進行更新。

優點

  • 耦合度高,通常用來實現一些響應式的效果;
  • 角色很明確,沒有事件排程中心作為中間者,目標物件Subject和觀察者Observer都要實現約定的成員方法;
  • 雙方聯絡緊密,目標物件的主動性很強,自己收集和維護觀察者,並在狀態變化時主動通知觀察者更新;

實現

// 目標物件
class Subject {
  constructor() {
    this.observers = []
  }

  add (observer) {
    this.observers.push(observer)
  }

  notify() {
    this.observers.map(observer => {
      if (observer && typeof observer.update === 'function') {
        observer.update()
      }
    })
  }

  remove(observer) {
    const idx = this.observers.findIndex(itm => itm === observer)
    if (idx !== -1) {
      this.observers.splice(idx, 1)
    }
  }
}

// 觀察者
class Observer {
  constructor(name) {
    this.name = name
  }

  update() {
    console.log(`${this.name} updated`)
  }
}

const subject = new Subject()
const o1 = new Observer('Nina')
const o2 = new Observer('Jack')

subject.add(o1)
subject.add(o2)

console.log('第一次通知:')
subject.notify()

subject.remove(o1)

console.log('刪除 Nina 後,再次通知:')
subject.notify()

輸出為:
image-20220311175804142.png

釋出-訂閱模式

基於一個事件(主題)通道,希望接收通知的物件 Subscriber 通過自定義事件訂閱主題,被啟用事件的物件 Publisher 通過釋出主題事件的方式通知各個訂閱該主題的 Subscriber 物件,這就是釋出訂閱模式。
釋出訂閱模式中有三個角色:釋出者 Publisher ,事件排程中心 Event Channel ,訂閱者 Subscriber

特點

  • 釋出訂閱模式中,對於釋出者Publisher和訂閱者Subscriber沒有特殊的約束,他們藉助事件排程中心提供的介面釋出和訂閱事件,互不瞭解對方是誰;
  • 鬆散耦合,靈活度高,常用作事件匯流排;
  • 易理解,可類比於DOM事件中的dispatchEventaddEventListener

缺點

  • 當事件型別越來越多時,難以維護,需要考慮事件命名的規範,也要防範資料流混亂。

實現

class EventEmitter {
  constructor() {
    this.list = {}
    if (!EventEmitter.instance) {
      EventEmitter.instance = this
    }
    return EventEmitter.instance
  }

  on(name, fn) {
    if (!this.list[name]) {
      this.list[name] = []
    }
    this.list[name].push(fn)
  }

  emit(...rest) {
    const name = ([].shift.call(rest))
    const fns = this.list[name] || []
    fns.forEach((fn) => {
      fn.apply(this, rest)
    })
  }

  off(name) {
    this.list[name] = []
  }

  clean() {
    this.list = {}
  }
}

const e1 = new EventEmitter()
e1.on('go', (name) => console.log(`${name} 走了`))
e1.on('come', (name) => console.log(`${name} 來了`))

e1.emit('go', 'Nina')
e1.emit('go', 'Jack')
e1.emit('come', 'Bill')

image.png

二者區別

  • 概念與實現上

    • 從概念上理解,兩者沒什麼不同,都在解決物件之間解耦,通過事件的方式在某個時間點進行觸發,監聽這個事件的訂閱者可以進行相應的操作。
    • 在實現上有所不同,觀察者模式對訂閱事件的訂閱者通過釋出者自身來維護,後續的一些列操作都要通過釋出者完成。釋出訂閱模式是訂閱者和釋出者中間會有一個事件匯流排,操作都要經過事件匯流排完成。
  • 耦合

    • 觀察者模式是面向目標和觀察者程式設計的,用於耦合目標和觀察者。觀察者和被觀察者之間還存在耦合,被觀察者還是知道觀察者的;
    • 釋出 - 訂閱模式是面向排程中心程式設計的,用於解耦釋出者和訂閱者。釋出者和訂閱者不需要知道對方的存在,通過訊息代理進行通訊,解耦更加徹底;
  • 關係

    • 觀察者模式的觀察者和被觀察者就像 商家-顧客 的關係,當商品有更新等,商家會直接通知訂閱的顧客。
    • 釋出訂閱模式的釋出者和訂閱者,就像 商家-APP-顧客 的關係,顧客(訂閱者)在APP上訂閱商品通知,待商品有更新時,商家(釋出者)通過APP通知訂閱的顧客(訂閱者)。
  • 從使用層面上講

    • 觀察者模式,多用於單個應用內部;
    • 釋出訂閱模式,更多的是一種跨應用的模式(cross-application pattern),比如訊息中介軟體;

相關文章