訂閱發佈模式和觀察者模式的區別

首選我們需要先了解兩者的定義和實現的方式,才能更好的區分兩者的不同點。

或許以前認爲訂閱發佈模式是觀察者模式的一種別稱,但是發展至今,概念已經有了不少區別。

訂閱發佈模式

軟件架構中,發佈-訂閱是一種消息範式,消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者)。而是將發佈的消息分爲不同的類別,無需瞭解哪些訂閱者(如果有的話)可能存在。同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需瞭解哪些發佈者(如果有的話)存在。

或許你用過 eventemitter、node 的 events、Backbone 的 events 等等,這些都是前端早期,比較流行的數據流通信方式,即訂閱發佈模式

從字面意思來看,我們需要首先訂閱,發佈者發佈消息後纔會收到發佈的消息。不過我們還需要一箇中間者來協調,從事件角度來說,這個中間者就是事件中心,協調發布者和訂閱者直接的消息通信。

完成訂閱發佈整個流程需要三個角色:

  • 發佈者
  • 事件中心
  • 訂閱者

以事件爲例,簡單流程如下:

發佈者->事件中心<=>訂閱者,訂閱者需要向事件中心訂閱指定的事件 -> 發佈者向事件中心發佈指定事件內容 -> 事件中心通知訂閱者 -> 訂閱者收到消息(可能是多個訂閱者),到此完成了一次訂閱發佈的流程。

簡單的代碼實現如下:

class Event {
  constructor() {
    // 所有 eventType 監聽器回調函數(數組)
    this.listeners = {}
  }
  /**
   * 訂閱事件
   * @param {String} eventType 事件類型
   * @param {Function} listener 訂閱後發佈動作觸發的回調函數,參數爲發佈的數據
   */
  on(eventType, listener) {
    if (!this.listeners[eventType]) {
      this.listeners[eventType] = []
    }
    this.listeners[eventType].push(listener)
  }
  /**
   * 發佈事件
   * @param {String} eventType 事件類型
   * @param {Any} data 發佈的內容
   */
  emit(eventType, data) {
    const callbacks = this.listeners[eventType]
    if (callbacks) {
      callbacks.forEach((c) => {
        c(data)
      })
    }
  }
}

const event = new Event()
event.on('open', (data) => {
  console.log(data)
})
event.emit('open', { open: true })

Event 可以理解爲事件中心,提供了訂閱和發佈功能。

訂閱者在訂閱事件的時候,只關注事件本身,而不關心誰會發布這個事件;發佈者在發佈事件的時候,只關注事件本身,而不關心誰訂閱了這個事件。

觀察者模式

觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個目標對象,當這個目標對象的狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新。

觀察者模式我們可能比較熟悉的場景就是響應式數據,如 Vue 的響應式、Mbox 的響應式。

觀察者模式有完成整個流程需要兩個角色:

  • 目標
  • 觀察者

簡單流程如下:

目標<=>觀察者,觀察者觀察目標(監聽目標)-> 目標發生變化-> 目標主動通知觀察者。

簡單的代碼實現如下:

/**
 * 觀察監聽一個對象的變化
 * @param {Object} subject 觀察的目標
 * @param {Function} callback 目標變化觸發的回調
 */
function observer(subject, callback) {
  Object.defineProperty(subject, 'description', {
    get() {
      return this.data.description
    },
    set(val) {
      this.data.description = val
      // 目標主動通知觀察者
      callback && callback(val)
    },
  })
}

可運行例子如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover"
    />
    <title></title>
  </head>
  <body>
    <div id="app">
      <div id="dom-one">
        原來的值
      </div>
      <br />
      <div id="dom-two">
        原來的值
      </div>
      <br />
      <button id="btn">改變</button>
    </div>
    <script>
      /**
       * 觀察監聽一個對象的變化
       * @param {Object} subject 觀察的目標
       * @param {Function} callback 目標變化觸發的回調
       */
      function observer(subject, callback) {
        Object.defineProperty(subject, 'description', {
          get() {
            return this.data.description
          },
          set(val) {
            this.data.description = val
            // 目標主動通知觀察者
            callback && callback(val)
          },
        })
      }

      const obj = {
        data: { description: '' },
      }

      observer(obj, value => {
        document.querySelector('#dom-one').innerHTML = value
        document.querySelector('#dom-two').innerHTML = value
      })

      btn.onclick = () => {
        obj.description = '改變了'
      }
    </script>
  </body>
</html>

兩者的區別在哪?

角色角度來看,訂閱發佈模式需要三種角色,發佈者、事件中心和訂閱者。二觀察者模式需要兩種角色,目標和觀察者,無事件中心負責通信。

從耦合度上來看,訂閱發佈模式是一個事件中心調度模式,訂閱者和發佈者是沒有直接關聯的,通過事件中心進行關聯,兩者是解耦的。而觀察者模式中目標和觀察者是直接關聯的,耦合在一起(有些觀念說觀察者是解耦,解耦的是業務代碼,不是目標和觀察者本身)。

兩者的優缺點?

優缺點都是從前端角度來看的。

訂閱發佈模式優點

  • 靈活

    由於訂閱發佈模式的發佈者和訂閱者是解耦的,只要引入訂閱發佈模式的事件中心,無論在何處都可以發佈訂閱。同時訂閱發佈者相互之間不影響。

訂閱發佈模式在使用不當的情況下,容易造成數據流混亂,所以纔有了 React 提出的單項數據流思想,就是爲了解決數據流混亂的問題。

訂閱發佈模式缺點

  • 容易導致代碼不好維護

    靈活是有點,同時也是缺點,使用不當就會造成數據流混亂,導致代碼不好維護。

  • 性能消耗更大

    訂閱發佈模式需要維護事件列隊,訂閱的事件越多,內存消耗越大。

觀察者模式優點

  • 響應式

    目標變化就會通知觀察者,這是觀察者最大的有點,也是因爲這個優點,觀察者模式在前端纔會這麼出名。

觀察者模式缺點

  • 不靈活

    相比訂閱發佈模式,由於目標和觀察者是耦合在一起的,所以觀察者模式需要同時引入目標和觀察者才能達到響應式的效果。而訂閱發佈模式只需要引入事件中心,訂閱者和發佈者可以不再一處(同一個頁面)。

參考文章

訂閱發佈模式和觀察者模式真的不一樣

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章