設(shè)計(jì)模式提供了軟件開發(fā)過程中的一些最佳實(shí)踐,可以幫助我們解決常見的編程問題,提高軟件的可維護(hù)性和可復(fù)用性,并使我們的代碼更加健壯和靈活。設(shè)計(jì)模式可以帶來以下好處:提高代碼的可讀性和可維護(hù)性、提高軟件的可復(fù)用性、提高開發(fā)效率、提高系統(tǒng)的靈活性和可擴(kuò)展性。今天我們講一下觀察者模式的具體應(yīng)用。
觀察者模式是一種軟件設(shè)計(jì)模式,它允許一個(gè)對象(稱為“主題”)管理其依賴項(xiàng)(稱為“觀察者”),它定義了對象之間的一對多依賴關(guān)系,當(dāng)一個(gè)對象狀態(tài)發(fā)生改變時(shí),其相關(guān)依賴項(xiàng)將會自動收到通知。這種模式提供了一種靈活的方式,將一個(gè)對象的狀態(tài)與依賴它的多個(gè)對象聯(lián)系起來。
在觀察者模式中,主題和觀察者之間建立了一種訂閱關(guān)系。主題負(fù)責(zé)維護(hù)其狀態(tài)并提供一個(gè)注冊表,用于存儲與其相關(guān)聯(lián)的觀察者對象。當(dāng)主題的狀態(tài)發(fā)生改變時(shí),它會自動通知所有與之相關(guān)聯(lián)的觀察者,并傳遞相應(yīng)的參數(shù)。觀察者接收到通知后,可以執(zhí)行相應(yīng)的操作來響應(yīng)主題狀態(tài)的改變。
意圖
觀察者模式是一種行為設(shè)計(jì)模式,允許你定義一種訂閱機(jī)制,可在對象事件發(fā)生時(shí)通知多個(gè)“觀察”該對象的其他對象。
問題
假如你有兩種類型的對象:顧客
和商店
。顧客對某個(gè)特定品牌的產(chǎn)品非常感興趣(例如最新型號的 iPhone 手機(jī)),而該產(chǎn)品很快將會在商店里出售。
顧客可以每天來商店看看產(chǎn)品是否到貨。但如果商品尚未到貨時(shí),絕大多數(shù)來到商店的顧客都會空手而歸。
另一方面,每次新產(chǎn)品到貨時(shí),商店可以向所有顧客發(fā)送郵件(可能會被視為垃圾郵件)。這樣,部分顧客就無需反復(fù)前往商店了,但也可能會惹惱對新產(chǎn)品沒有興趣的其他顧客。
我們似乎遇到了一個(gè)矛盾:要么讓顧客浪費(fèi)時(shí)間檢查產(chǎn)品是否到貨,要么讓商店浪費(fèi)資源去通知沒有需求的顧客。
解決方案
擁有一些值得關(guān)注的狀態(tài)的對象通常被稱為目標(biāo),由于它要將自身的狀態(tài)改變通知給其他對象,我們也將其稱為發(fā)布者(publisher)。所有希望關(guān)注發(fā)布者狀態(tài)變化的其他對象被稱為訂閱者(subscribers)。
觀察者模式建議你為發(fā)布者類添加訂閱機(jī)制,讓每個(gè)對象都能訂閱或取消訂閱發(fā)布者事件流。不要害怕!這并不像聽上去那么復(fù)雜。實(shí)際上,該機(jī)制包括 1)一個(gè)用于存儲訂閱者對象引用的列表成員變量;2)幾個(gè)用于添加或刪除該列表中訂閱者的公有方法。
訂閱機(jī)制允許對象訂閱事件通知。
現(xiàn)在,無論何時(shí)發(fā)生了重要的發(fā)布者事件,它都要遍歷訂閱者并調(diào)用其對象的特定通知方法。
實(shí)際應(yīng)用中可能會有十幾個(gè)不同的訂閱者類跟蹤著同一個(gè)發(fā)布者類的事件,你不會希望發(fā)布者與所有這些類相耦合的。此外如果他人會使用發(fā)布者類,那么你甚至可能會對其中的一些類一無所知。
因此,所有訂閱者都必須實(shí)現(xiàn)同樣的接口,發(fā)布者僅通過該接口與訂閱者交互。接口中必須聲明通知方法及其參數(shù),這樣發(fā)布者在發(fā)出通知時(shí)還能傳遞一些上下文數(shù)據(jù)。
發(fā)布者調(diào)用訂閱者對象中的特定通知方法來通知訂閱者。
如果你的應(yīng)用中有多個(gè)不同類型的發(fā)布者,且希望訂閱者可兼容所有發(fā)布者,那么你甚至可以進(jìn)一步讓所有發(fā)布者遵循同樣的接口。該接口僅需描述幾個(gè)訂閱方法即可。這樣訂閱者就能在不與具體發(fā)布者類耦合的情況下通過接口觀察發(fā)布者的狀態(tài)。
真實(shí)世界類比
如果你訂閱了一份雜志或報(bào)紙,那就不需要再去報(bào)攤查詢新出版的刊物了。出版社(即應(yīng)用中的“發(fā)布者”)會在刊物出版后(甚至提前)直接將最新一期寄送至你的郵箱中。
出版社負(fù)責(zé)維護(hù)訂閱者列表,了解訂閱者對哪些刊物感興趣。當(dāng)訂閱者希望出版社停止寄送新一期的雜志時(shí),他們可隨時(shí)從該列表中退出。
我們看一段代碼示例,然后再通過示例進(jìn)行分析。在JavaScript中,我們可以使用原型或類來實(shí)現(xiàn)觀察者模式。下面是一個(gè)使用原型的實(shí)現(xiàn)示例:
// 觀察者接口
var Observer = function() {};
Observer.prototype.update = function(data) {};
// 具體觀察者
var ConcreteObserver1 = function() {};
ConcreteObserver1.prototype = Object.create(Observer.prototype);
ConcreteObserver1.prototype.constructor = ConcreteObserver1;
ConcreteObserver1.prototype.update = function(data) {
console.log('ConcreteObserver1 received data: ' + data);
};
// 具體觀察者
var ConcreteObserver2 = function() {};
ConcreteObserver2.prototype = Object.create(Observer.prototype);
ConcreteObserver2.prototype.constructor = ConcreteObserver2;
ConcreteObserver2.prototype.update = function(data) {
console.log('ConcreteObserver2 received data: ' + data);
};
// 主題
var Subject = function() {
this.observers = [];
};
Subject.prototype.registerObserver = function(observer) {
this.observers.push(observer);
};
Subject.prototype.notifyObservers = function(data) {
for (var i = 0; i < this.observers.length; i++) {
this.observers[i].update(data);
}
};
// 具體主題
var ConcreteSubject = function() {};
ConcreteSubject.prototype = Object.create(Subject.prototype);
ConcreteSubject.prototype.constructor = ConcreteSubject;
ConcreteSubject.prototype.setState = function(data) {
this.notifyObservers(data);
};
在上面的代碼中,我們首先定義了一個(gè)Observer接口,它包含一個(gè)update方法。然后我們創(chuàng)建了兩個(gè)具體的觀察者ConcreteObserver1和ConcreteObserver2,它們都實(shí)現(xiàn)了Observer接口的update方法。接著我們定義了一個(gè)主題Subject,它包含一個(gè)觀察者數(shù)組和一個(gè)注冊方法registerObserver,以及一個(gè)通知方法notifyObservers。最后我們創(chuàng)建了一個(gè)具體主題ConcreteSubject,它繼承了Subject的原型并實(shí)現(xiàn)了一個(gè)setState方法,該方法調(diào)用通知方法來通知所有觀察者狀態(tài)改變。
在上面的示例中,我們使用了原型繼承來實(shí)現(xiàn)Observer接口和具體的觀察者類。在實(shí)際應(yīng)用中,我們也可以使用類繼承或ES6的class語法來實(shí)現(xiàn)這些類。另外,在具體主題ConcreteSubject中,我們通過調(diào)用notifyObservers方法來通知所有觀察者狀態(tài)改變,這個(gè)方法可以傳遞一個(gè)參數(shù)作為通知的內(nèi)容。在具體觀察者的update方法中,我們可以根據(jù)傳遞的參數(shù)來執(zhí)行相應(yīng)的操作。
除了使用JavaScript實(shí)現(xiàn)觀察者模式外,我們還可以在其他編程語言和框架中找到這種模式的實(shí)現(xiàn)。例如,Redis的訂閱模型和WebSocket請求都使用了類似的方式來實(shí)現(xiàn)主題和觀察者之間的訂閱關(guān)系。這些實(shí)現(xiàn)方式都允許客戶端訂閱特定主題,并在主題狀態(tài)發(fā)生改變時(shí)自動接收通知。
觀察者模式適合應(yīng)用場景
當(dāng)一個(gè)對象狀態(tài)的改變需要改變其他對象,或?qū)嶋H對象是事先未知的或動態(tài)變化的時(shí),可使用觀察者模式。
當(dāng)你使用圖形用戶界面類時(shí)通常會遇到一個(gè)問題。比如,你創(chuàng)建了自定義按鈕類并允許客戶端在按鈕中注入自定義代碼,這樣當(dāng)用戶按下按鈕時(shí)就會觸發(fā)這些代碼。
觀察者模式允許任何實(shí)現(xiàn)了訂閱者接口的對象訂閱發(fā)布者對象的事件通知。你可在按鈕中添加訂閱機(jī)制,允許客戶端通過自定義訂閱類注入自定義代碼。
當(dāng)應(yīng)用中的一些對象必須觀察其他對象時(shí),可使用該模式。但僅能在有限時(shí)間內(nèi)或特定情況下使用。
訂閱列表是動態(tài)的,因此訂閱者可隨時(shí)加入或離開該列表。
實(shí)現(xiàn)方式
-
仔細(xì)檢查你的業(yè)務(wù)邏輯,試著將其拆分為兩個(gè)部分:獨(dú)立于其他代碼的核心功能將作為發(fā)布者;其他代碼則將轉(zhuǎn)化為一組訂閱類。
-
聲明訂閱者接口。該接口至少應(yīng)聲明一個(gè)
update
方法。 -
聲明發(fā)布者接口并定義一些接口來在列表中添加和刪除訂閱對象。記住發(fā)布者必須僅通過訂閱者接口與它們進(jìn)行交互。
-
確定存放實(shí)際訂閱列表的位置并實(shí)現(xiàn)訂閱方法。通常所有類型的發(fā)布者代碼看上去都一樣,因此將列表放置在直接擴(kuò)展自發(fā)布者接口的抽象類中是顯而易見的。具體發(fā)布者會擴(kuò)展該類從而繼承所有的訂閱行為。
但是,如果你需要在現(xiàn)有的類層次結(jié)構(gòu)中應(yīng)用該模式,則可以考慮使用組合的方式:將訂閱邏輯放入一個(gè)獨(dú)立的對象,然后讓所有實(shí)際訂閱者使用該對象。
-
創(chuàng)建具體發(fā)布者類。每次發(fā)布者發(fā)生了重要事件時(shí)都必須通知所有的訂閱者。
-
在具體訂閱者類中實(shí)現(xiàn)通知更新的方法。絕大部分訂閱者需要一些與事件相關(guān)的上下文數(shù)據(jù)。這些數(shù)據(jù)可作為通知方法的參數(shù)來傳遞。
但還有另一種選擇。訂閱者接收到通知后直接從通知中獲取所有數(shù)據(jù)。在這種情況下,發(fā)布者必須通過更新方法將自身傳遞出去。另一種不太靈活的方式是通過構(gòu)造函數(shù)將發(fā)布者與訂閱者永久性地連接起來。
-
客戶端必須生成所需的全部訂閱者,并在相應(yīng)的發(fā)布者處完成注冊工作。
觀察者模式優(yōu)缺點(diǎn)
-
優(yōu)點(diǎn):
-
降低目標(biāo)與觀察者之間的耦合關(guān)系
-
支持“廣播通信”
-
符合開閉原則。
-
-
確定:
-
通知可能會花費(fèi)很長時(shí)間
-
循環(huán)依賴的問題
-
沒有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對象是怎么發(fā)生變化的
-
總結(jié)一下,觀察者模式適用于任何需要實(shí)現(xiàn)一對多依賴關(guān)系的場景,使得主題的狀態(tài)改變可以自動通知給所有的觀察者。
-
訂閱/發(fā)布系統(tǒng):觀察者模式可以用于實(shí)現(xiàn)訂閱/發(fā)布系統(tǒng)。主題可以代表各種事件或消息,觀察者可以訂閱感興趣的主題并接收相關(guān)的通知。
-
實(shí)時(shí)通信:觀察者模式可以用于實(shí)現(xiàn)實(shí)時(shí)通信。當(dāng)某個(gè)事件發(fā)生時(shí),相關(guān)的觀察者可以立即得到通知并做出相應(yīng)的響應(yīng)。
-
數(shù)據(jù)綁定:在圖形用戶界面開發(fā)中,觀察者模式可以用于實(shí)現(xiàn)數(shù)據(jù)綁定。當(dāng)某個(gè)數(shù)據(jù)源發(fā)生改變時(shí),相關(guān)的視圖可以自動更新。
-
事件驅(qū)動系統(tǒng):觀察者模式可以用于實(shí)現(xiàn)事件驅(qū)動系統(tǒng)。當(dāng)某個(gè)事件觸發(fā)時(shí),相關(guān)的觀察者可以收到通知并執(zhí)行相應(yīng)的操作。
-
異步消息處理:在分布式系統(tǒng)中,觀察者模式可以用于實(shí)現(xiàn)異步消息處理。當(dāng)某個(gè)消息到達(dá)時(shí),相關(guān)的觀察者可以收到通知并處理該消息。
-
存儲
+關(guān)注
關(guān)注
13文章
4226瀏覽量
85576 -
軟件設(shè)計(jì)
+關(guān)注
關(guān)注
3文章
58瀏覽量
17753 -
代碼
+關(guān)注
關(guān)注
30文章
4722瀏覽量
68231 -
模式
+關(guān)注
關(guān)注
0文章
65瀏覽量
13365
原文標(biāo)題:觀察者模式,超詳細(xì)!
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論