淺談發(fā)布訂閱模式與觀察者模式
背景
設(shè)計模式并非是軟件開發(fā)的專業(yè)術(shù)語,實際上,“模式”最早誕生于建筑學。
設(shè)計模式的定義是:在面向?qū)ο筌浖O(shè)計過程中針對特定問題的簡潔而優(yōu)雅的解決方案。通俗一點說,設(shè)計模式是在某種場合下對某個問題的一種解決方案。如果再通俗一點說,設(shè)計模式就是給面向?qū)ο筌浖_發(fā)中的一些好的設(shè)計取個名字。
這些“好的設(shè)計”并不是誰發(fā)明的,而是早已存在于軟件開發(fā)中。一個稍有經(jīng)驗的程序員也許在不知不覺中數(shù)次使用過這些設(shè)計模式。GoF(Gang of Four--四人組,《設(shè)計模式》幾位作者)最大的功績是把這些“好的設(shè)計”從浩瀚的面向?qū)ο笫澜缰刑暨x出來,并且給予它們一個好聽又好記的名字。
設(shè)計模式并不直接用來完成代碼的編寫,而是描述在各種不同情況下,要怎么解決問題的一種方案,他不是一個死的機制,他是一種思想,一種寫代碼的形式。每種語言對于各種設(shè)計模式都有他們自己的實現(xiàn)方式,對于某些設(shè)計模式來說,可能在某些語言下并不適用,比如工廠方法模式對于javascript。模式應該用在正確的地方。而哪些才算正確的地方,只有在我們深刻理解了模式的意圖之后,再結(jié)合項目的實際場景才會知道。。
模式的社區(qū)一直在發(fā)展。GoF在1995年提出了23種設(shè)計模式,但模式不僅僅局限于這23種,后面增加到了24種。在這20多年的時間里,也許有更多的模式已經(jīng)被人發(fā)現(xiàn)并總結(jié)了出來,比如一些JavaScript 圖書中會提到模塊模式、沙箱模式等。這些“模式”能否被世人公認并流傳下來,還有待時間驗證。
觀察者模式(Observer Pattern)
觀察者模式定義了對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知,并自動更新。觀察者模式屬于行為型模式,行為型模式關(guān)注的是對象之間的通訊,觀察者模式就是觀察者和被觀察者之間的通訊。
觀察者模式有一個別名叫“發(fā)布-訂閱模式”,或者說是“訂閱-發(fā)布模式”,訂閱者和訂閱目標是聯(lián)系在一起的,當訂閱目標發(fā)生改變時,逐個通知訂閱者。我們可以用報紙期刊的訂閱來形象的說明,當你訂閱了一份報紙,每天都會有一份最新的報紙送到你手上,有多少人訂閱報紙,報社就會發(fā)多少份報紙,報社和訂報紙的客戶就是上面文章開頭所說的“一對多”的依賴關(guān)系。
發(fā)布訂閱模式(Pub-Sub Pattern)
其實24種基本的設(shè)計模式中并沒有發(fā)布訂閱模式,上面也說了,他只是觀察者模式的一個別稱。
但是經(jīng)過時間的沉淀,似乎他已經(jīng)強大了起來,已經(jīng)獨立于觀察者模式,成為另外一種不同的設(shè)計模式。
在現(xiàn)在的發(fā)布訂閱模式中,稱為發(fā)布者的消息發(fā)送者不會將消息直接發(fā)送給訂閱者,這意味著發(fā)布者和訂閱者不知道彼此的存在。在發(fā)布者和訂閱者之間存在第三個組件,稱為消息代理或調(diào)度中心或中間件,它維持著發(fā)布者和訂閱者之間的聯(lián)系,過濾所有發(fā)布者傳入的消息并相應地分發(fā)它們給訂閱者。
舉一個例子,你在微博上關(guān)注了A,同時其他很多人也關(guān)注了A,那么當A發(fā)布動態(tài)的時候,微博就會為你們推送這條動態(tài)。A就是發(fā)布者,你是訂閱者,微博就是調(diào)度中心,你和A是沒有直接的消息往來的,全是通過微博來協(xié)調(diào)的(你的關(guān)注,A的發(fā)布動態(tài))。
觀察者模式和發(fā)布訂閱模式有什么區(qū)別?
我們先來看下這兩個模式的實現(xiàn)結(jié)構(gòu):
觀察者模式:觀察者(Observer)直接訂閱(Subscribe)主題(Subject),而當主題被激活的時候,會觸發(fā)(Fire Event)觀察者里的事件。
發(fā)布訂閱模式:訂閱者(Subscriber)把自己想訂閱的事件注冊(Subscribe)到調(diào)度中心(Topic),當發(fā)布者(Publisher)發(fā)布該事件(Publish topic)到調(diào)度中心,也就是該事件觸發(fā)時,由調(diào)度中心統(tǒng)一調(diào)度(Fire Event)訂閱者注冊到調(diào)度中心的處理代碼。
我們再來看下這兩個模式的代碼案例:(獵人發(fā)布與訂閱任務)
觀察者模式:
//有一家獵人工會,其中每個獵人都具有發(fā)布任務(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(item, index){ item(money) }) } Hunter.prototype.subscribe = function (targrt, fn){ console.log(this.level + '獵人' + this.name + '訂閱了' + targrt.name) targrt.list.push(fn) } //獵人工會走來了幾個獵人 let hunterMing = new Hunter('小明', '黃金') let hunterJin = new Hunter('小金', '白銀') let hunterZhang = new Hunter('小張', '黃金') let hunterPeter = new Hunter('Peter', '青銅') //Peter等級較低,可能需要幫助,所以小明,小金,小張都訂閱了Peter hunterMing.subscribe(hunterPeter, function(money){ console.log('小明表示:' + (money > 200 ? '' : '暫時很忙,不能') + '給予幫助') }) hunterJin.subscribe(hunterPeter, function(){ console.log('小金表示:給予幫助') }) hunterZhang.subscribe(hunterPeter, function(){ console.log('小張表示:給予幫助') }) //Peter遇到困難,賞金198尋求幫助 hunterPeter.publish(198) //獵人們(觀察者)關(guān)聯(lián)他們感興趣的獵人(目標對象),如Peter,當Peter有困難時,會自動通知給他們(觀察者)
發(fā)布訂閱模式:
//定義一家獵人工會 //主要功能包括任務發(fā)布大廳(topics),以及訂閱任務(subscribe),發(fā)布任務(publish) let HunterUnion = { type: 'hunt', topics: Object.create(null), subscribe: function (topic, fn){ if(!this.topics[topic]){ this.topics[topic] = []; } this.topics[topic].push(fn); }, publish: function (topic, money){ if(!this.topics[topic]) return; for(let fn of this.topics[topic]){ fn(money) } } } //定義一個獵人類 //包括姓名,級別 function Hunter(name, level){ this.name = name this.level = level } //獵人可在獵人工會發(fā)布訂閱任務 Hunter.prototype.subscribe = function (topic, fn){ console.log(this.level + '獵人' + this.name + '訂閱了狩獵' + topic + '的任務') HunterUnion.subscribe(topic, fn) } Hunter.prototype.publish = function (topic, money){ console.log(this.level + '獵人' + this.name + '發(fā)布了狩獵' + topic + '的任務') HunterUnion.publish(topic, money) } //獵人工會走來了幾個獵人 let hunterMing = new Hunter('小明', '黃金') let hunterJin = new Hunter('小金', '白銀') let hunterZhang = new Hunter('小張', '黃金') let hunterPeter = new Hunter('Peter', '青銅') //小明,小金,小張分別訂閱了狩獵tiger的任務 hunterMing.subscribe('tiger', function(money){ console.log('小明表示:' + (money > 200 ? '' : '不') + '接取任務') }) hunterJin.subscribe('tiger', function(money){ console.log('小金表示:接取任務') }) hunterZhang.subscribe('tiger', function(money){ console.log('小張表示:接取任務') }) //Peter訂閱了狩獵sheep的任務 hunterPeter.subscribe('sheep', function(money){ console.log('Peter表示:接取任務') }) //Peter發(fā)布了狩獵tiger的任務 hunterPeter.publish('tiger', 198) //獵人們發(fā)布(發(fā)布者)或訂閱(觀察者/訂閱者)任務都是通過獵人工會(調(diào)度中心)關(guān)聯(lián)起來的,他們沒有直接的交流。
觀察者模式和發(fā)布訂閱模式最大的區(qū)別就是發(fā)布訂閱模式有個事件調(diào)度中心。
觀察者模式由具體目標調(diào)度,每個被訂閱的目標里面都需要有對觀察者的處理,這種處理方式比較直接粗暴,但是會造成代碼的冗余。
而發(fā)布訂閱模式中統(tǒng)一由調(diào)度中心進行處理,訂閱者和發(fā)布者互不干擾,消除了發(fā)布者和訂閱者之間的依賴。這樣一方面實現(xiàn)了解耦,還有就是可以實現(xiàn)更細粒度的一些控制。比如發(fā)布者發(fā)布了很多消息,但是不想所有的訂閱者都接收到,就可以在調(diào)度中心做一些處理,類似于權(quán)限控制之類的。還可以做一些節(jié)流操作。
觀察者模式是不是發(fā)布訂閱模式
網(wǎng)上關(guān)于這個問題的回答,出現(xiàn)了兩極分化,有認為發(fā)布訂閱模式就是觀察者模式的,也有認為觀察者模式和發(fā)布訂閱模式是真不一樣的。
其實我不知道發(fā)布訂閱模式是不是觀察者模式,就像我不知道辨別模式的關(guān)鍵是設(shè)計意圖還是設(shè)計結(jié)構(gòu)(理念),雖然《JavaScript設(shè)計模式與開發(fā)實踐》一書中說了分辨模式的關(guān)鍵是意圖而不是結(jié)構(gòu)
。
如果以結(jié)構(gòu)來分辨模式,發(fā)布訂閱模式相比觀察者模式多了一個中間件訂閱器,所以發(fā)布訂閱模式是不同于觀察者模式的;如果以意圖來分辨模式,他們都是實現(xiàn)了對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知,并自動更新
,那么他們就是同一種模式,發(fā)布訂閱模式是在觀察者模式的基礎(chǔ)上做的優(yōu)化升級。
不過,不管他們是不是同一個設(shè)計模式,他們的實現(xiàn)方式確實有差別,我們在使用的時候應該根據(jù)場景來判斷選擇哪個。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于javascript實現(xiàn)彩票隨機數(shù)生成(升級版)
這篇文章主要為大家詳細介紹了基于javascript實現(xiàn)彩票隨機數(shù)生成的相關(guān)資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-01-01通過設(shè)置CSS中的position屬性來固定層的位置
position 屬性規(guī)定元素的定位類型,這個屬性定義建立元素布局所用的定位機制,本文給大家介紹通過設(shè)置CSS中的position屬性來固定層的位置,感興趣的朋友一起學習吧2015-12-12用Javascript實現(xiàn)Sleep暫停功能代碼
ie和firefox都可以使用,有興趣可以試試2010-09-09Extjs4中tree的拖拽功能(可以兩棵樹之間拖拽) 簡單實例
這篇文章主要介紹了Extjs4中tree的拖拽功能簡單實例,有需要的朋友可以參考一下2013-12-12