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