發(fā)布(Publish)/訂閱(Subscribe)模式是一種和觀察者(Observer)模式很相似的設(shè)計模式,以至于很多人把這兩種模式看作是同一種模式。
在觀察者模式中的Subject就像一個發(fā)布者(Publisher),而觀察者(Observer)完全可以看作一個訂閱者(Subscriber)。subject通知觀察者時,就像一個發(fā)布者通知他的訂閱者。
1.觀察者模式和發(fā)布/訂閱模式的概念
-
觀察者模式
目標(biāo)和觀察者是基類,目標(biāo)提供維護觀察者的一系列方法,觀察者提供更新接口。具體觀察者和具體目標(biāo)繼承各自的基類,然后具體觀察者把自己注冊到具體目標(biāo)里,在具體目標(biāo)發(fā)生變化時候,調(diào)度觀察者的更新方法。
observer.png -
發(fā)布/訂閱模式
訂閱者把自己想訂閱的事件注冊到調(diào)度中心,當(dāng)該事件觸發(fā)時候,發(fā)布者發(fā)布該事件到調(diào)度中心(順帶上下文),由調(diào)度中心統(tǒng)一調(diào)度訂閱者注冊到調(diào)度中心的處理代碼。
pubsub.png
我們再用一個圖來直觀地感受一下兩種模式之間的區(qū)別:
觀察者模式和發(fā)布/訂閱模式的區(qū)別是:
- 從上面的圖片中可以看到,最大的區(qū)別是調(diào)度的地方。雖然兩種模式都存在訂閱者和發(fā)布者(具體觀察者可認為是訂閱者、具體目標(biāo)可認為是發(fā)布者),但是觀察者模式是由具體目標(biāo)調(diào)度的,而發(fā)布/訂閱模式是統(tǒng)一由調(diào)度中心調(diào)的,所以觀察者模式的訂閱者與發(fā)布者之間是存在依賴的,而發(fā)布/訂閱模式則不會。
- 在發(fā)布訂閱模式中,組件是松散耦合的,正好和觀察者模式相反。
2.觀察者模式實現(xiàn)
下面這段代碼摘抄自JavaScript 設(shè)計模式一書
-
2.1對觀察者和被觀察者進行建模:
2-1.png
- 2.2 擴展函數(shù)
function extend(extension,obj){
for(var key in extension){
obj[key]=extension[key]; }
}
- 2.3使用范例
html:
<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>
script:
//注意要把2-1和2-2封裝好的函數(shù)copy進來
//我們DOM元素的引用
var controlCheckbox=document.getElementById("mainCheckbox"),
addBtn=document.getElementById("addNewObserver"),
container=document.getElementById("observersContainer");
//具體的被觀察者 //Subject類擴展controlCheckbox類
extend(new Subject(),controlCheckbox);
//點擊checkbox將會觸發(fā)對觀察者的通知
controlCheckbox["onclick"]=new Function("controlCheckbox.notify(controlCheckbox.checked)");
addBtn["onclick"]=AddNewObserver;
//具體的觀察者
function AddNewObserver(){
//建立一個新的用于增加的checkbox
var check =document.createElement("input"); check.type="checkbox";
//使用Observer類擴展
extend(new Observer(),check);
//使用定制的Update函數(shù)重載
check.update=function(value){
this.checked=value;
};
//增加新的觀察者到我們主要的被觀察者的觀察者列表中
controlCheckbox.addObserver(check);
//將元素添加到容器的最后
container.appendChild(check);
}
運行結(jié)果截圖:
3.發(fā)布/訂閱模式實現(xiàn)
發(fā)布/訂閱在JavaScript的生態(tài)系統(tǒng)中非常合適,主要是因為作為核心的ECMAScript 實現(xiàn)是事件驅(qū)動的。尤其是在瀏覽器環(huán)境下更是如此,因為DOM 使用事件作為其主要的用于腳本的交互API。
- 測試
var messageLogger=function(topics,data){
console.log("Logging:"+topics+":"+data);
};
var subscription=pubsub.subscribe("inbox/newMessage",messageLogger);
pubsub.publish("inbox/newMessage","helloworld!");
// or
pubsub.publish("inbox/newMessage",["test","a","b","c"]);
// or
pubsub.publish("inbox/newMessage",{
sender:"hello@google.com",
body:"Heyagain!"
});
-
運行結(jié)果
pubsub-res.png
關(guān)于發(fā)布/訂閱模式的漸進實現(xiàn)大家可以參考JS設(shè)計模式之訂閱模式
參考文章:
觀察者模式與發(fā)布/訂閱模式區(qū)別
【Javascript設(shè)計模式3】-觀察者模式
《JavaScript 設(shè)計模式》一書