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

背景

設計模式的定義: 在面向對象軟件設計過程中針對特定問題的簡潔而優雅的解決方案。

設計模式並不能直接用來完成代碼的編寫,而是描述在各種不同情況下,要怎麼解決問題的一種方案,它不是一個死的機制,它是一種思想,一種代碼的形式。

每種語言對於各種設計模式都要它們自己的實現方式,對於某些設計模式來說,可能在某些語言下並不適用,比如工廠模式就不適用於JavaSctipt。模式應該用在正確的地方,而所謂正確的地方只有我們深刻理解模式的意圖後,再結合項目的實際場景才知道。

觀察者模式 (Observer Pattern)

觀察者模式定義了對象間的一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴它的對象都將得到通知,並自動更新。

觀察者模式屬於行爲模式,行爲模式關注的是對象之間的通訊,觀察者模式就是觀察者和被觀察者之間的通訊。

觀察者模式還有一個別名叫“發佈-訂閱模式”,又或者“訂閱-發佈模式”,訂閱者和訂閱目標是聯繫在一起的,當訂閱目標發生改變時,逐各通知訂閱者。
我們用報紙期刊的訂閱來舉例說明,當你訂閱一份報紙,每天都會有一份最新的報紙送到你手上,有多少人訂閱報紙,報社就會發多少份報紙,報社和訂報紙的客戶就是定義裏描述的“一對多”的依賴關係。

發佈-訂閱模式 (Pub-Sub Pattern)

其實24種基本設計模式中,並沒有發佈-訂閱模式,上面也解釋了,它只是觀察者模式的一個別稱。但經過時間的沉澱,它已經強大起來,已經獨立於觀察者模式,成爲一種新的設計模式。

在現在的發佈-訂閱模式中,發佈者的消息不會直接發送給訂閱者,這意味着發佈者和訂閱者都不知道彼此的存在。在發佈者和訂閱者之間存在第三個組件,稱爲消息代理或調度中心或中間件,它維持着發佈者和訂閱者之間的聯繫,過濾所有發佈者傳入的消息,並相應的分發給它們的訂閱者。
舉個例子,你在微博上關注了A,同時其他很多人也關注了A,當A發佈動態時,微博就會爲你們推送這條動態。A就是發佈者,你就是訂閱者,微博是調度中心,你和A是沒有直接的消息往來的,全是通過微博來協調的。

觀察者模式和發佈-訂閱模式有什麼區別?

我們先來看下者兩個模式的實現結構:
觀察者模式和發佈-訂閱模式的實現結構

觀察者模式: 觀察者(Observer)直接訂閱(Subscribe)主體(Subject),而當主體被激活時,會觸發(Fire Event)觀察者裏的事件。

發佈-訂閱模式: 訂閱者(Subscriber)把自己想訂閱的事件註冊(Subscribe)到調度中心(Topic),當發佈者(Publisher)發佈該事件(Publish topic)到調度中心,也就是該事件觸發時,由調度中心統一調度(Fire Event)訂閱者註冊到調度中心的處理代碼。

案例

觀察者模式

// 有一家獵人公會,其中每個獵人都具體發佈任務(publish),訂閱任務(subscribe)的功能
// 它們都有一個訂閱列表記錄誰訂閱了自己

// 定義一個獵人,包括姓名、級別、訂閱列表
function Hunter(name, level) {
  this,name = name
  this.level = level
  this.list = []
}
Hunter.prototype.publish = function (money) {
  console.log(this,level + '獵人: ' + this.name + '尋求幫助')
  this.list.forEach(function (callback) {
    callback && callback(money)
  })
}
Hunter.prototype.subscribe = function (target, callback) {
  console.log(this.level + '獵人: ' + this.name + '訂閱了: ' + target.name)
  target.list.push(callback)
}

// 獵人公會走註冊了幾個獵人
var hunterZhang = new Hunter('張三', '鑽石')
var hunterLi = new Hunter('李四', '黃金')
var hunterWang = new Hunter('王五', '白銀')
var hunterZhao = new Hunter('趙六', '青銅')

// 趙六等級較低,可能需要幫助,所以張三、李四、王五都訂閱了趙六
hunterZhang.subscribe(hunterZhao, function (money) {
  console.log('小明表示: ' + (money > 200 ? '' : '暫時很忙,不能') + '給予幫助')
})
hunterLi.subscribe(hunterZhao, function () {
  console.log('李四表示: 給予幫助')
})
hunterWang.subscribe(hunterZhao, function () {
  console.log('王五表示: 給予幫助')
})

// 趙六遇到困難,懸賞198尋求幫助
hunterZhao.publish(198)

// 獵人們(觀察者)關聯他們感興趣的獵人(目標對象),如趙六,當趙六有困難時,會自動通知給他們(觀察者)

發佈-訂閱模式

// 定義了一家獵人公會
// 主要功能包含任務發佈大廳(topics)、訂閱任務(subscribe)、發佈任務(publish)
var HunterUnion = {
  type: 'hunt',
  topics: Object.create(null),
  subscribe: function (topic, callback) {
    if (!this.topics[topic]) {
      this.topics[topic] = []
    }
    this.topics[topic].push(callback)
  },
  publish: function (topic, money) {
    if (!this.topics[topic]) {
      return
    }
    for(var cb of this.topics[topic]) {
      cb(money)
    }
  }
}

// 定義一個獵人類,包括姓名和級別
function Hunter(name, level) {
  this.name = name
  this.level = level
}
// 獵人可以在獵人公會發布、訂閱任務
Hunter.prototype.subscribe = function (task, fn) {
  console.log(this.level + '獵人: ' + this.name + '訂閱了狩獵: ' + task + '的任務')
  HunterUnion.subscribe(task, fn)
}
Hunter.prototype.publish = function (task, money) {
  console.log(this.level + '獵人: ' + this.name + '發佈了狩獵: ' + task + '的任務')
  HunterUnion.publish(task, money)
}

//獵人工會註冊了幾個獵人
let hunterZhang = new Hunter('張三', '鑽石')
let hunterLi = new Hunter('李四', '黃金')
let hunterWang = new Hunter('王五', '白銀')
let hunterZhao = new Hunter('趙六', '青銅')

//張三,李四,王五分別訂閱了狩獵tiger的任務
hunterZhang.subscribe('tiger', function(money){
  console.log('張三表示:' + (money > 200 ? '' : '不') + '接取任務')
})
hunterLi.subscribe('tiger', function(money){
  console.log('李四表示:接取任務')
})
hunterWang.subscribe('tiger', function(money){
  console.log('王五表示:接取任務')
})
//趙六訂閱了狩獵sheep的任務
hunterZhao.subscribe('sheep', function(money){
  console.log('趙六表示:接取任務')
})

//趙六發布了狩獵tiger的任務
hunterZhao.publish('tiger', 198)

//獵人們發佈(發佈者)或訂閱(觀察者/訂閱者)任務都是通過獵人工會(調度中心)關聯起來的,他們沒有直接的交流。

觀察者模式和發佈-訂閱模式最大的區別: 發佈-訂閱模式有事件調度中心

觀察者模式由具體目標調度,每個被訂閱的目標裏面都需要有對觀察者的處理,這種處理方式可能會造成代碼的冗餘。

發佈-訂閱模式中,統一由調度中進行處理,訂閱者和發佈者互不干擾,消除了發佈者和訂閱者之間的依賴。這樣一方面實現瞭解耦,另一方面可以實現更加細粒度的控制。比如發佈者發佈了很多消息,但不是所有的訂閱者都希望接收到,就可以在調度中心做一些處理,類似權限控制之類的。還可以做一些節流操作。

觀察者模式是不是發佈-訂閱模式?

《JavaScript設計模式與開發實踐》一書中說到分辨模式的關鍵是意圖而不是結構

如果以結構來分辨模式,發佈-訂閱模式比觀察者模式多了一個調度中心,所以發佈-訂閱模式不同於觀察者模式。

如果以意圖來分辨模式,它們都實現了對象間一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴它的對象都將得到通知,並自動更新,那麼它們就是同一種模式,發佈-訂閱模式是在觀察者模式的基礎上做了優化升級。

不過不管它們是不是同一個設計模式,它們的實現方式的確是有區別,我們在使用時應該根據應用場景來判斷選擇哪個。

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