设计模式中的观察者模式
觀察者模式是一種軟件設計模式,其中一個名為主體(Subject)的對象維護其依賴項列表,稱為觀察者,并通常通過調用它們(observers)的方法之一來自動通知它們任何狀態更改。
觀察者模式主要用于在“事件驅動”軟件中實現分布式事件處理系統。在這些系統中,主體 Subject 通常被稱為“事件流(stream of events)”或“事件流源”,而觀察者被稱為“事件接收器”。
流命名法暗示了一種物理設置,其中觀察者在物理上是分開的,并且無法控制從主題/流源發出的事件。
這種模式非常適合任何進程,其中數據從啟動時 CPU 不可用的某些輸入到達,而是“隨機”到達(HTTP 請求、GPIO 數據、來自鍵盤/鼠標/的用戶輸入…、分布式數據庫)和區塊鏈,…)。
大多數現代編程語言都包含實現觀察者模式組件的內置“事件”結構。雖然不是強制性的,但大多數“觀察者”實現將使用后臺線程監聽主題事件和內核提供的其他支持機制(Linux epoll,…)。
觀察者設計模式是二十三個著名的“四人幫”設計模式之一,描述了如何解決反復出現的設計挑戰,以設計靈活且可重用的面向對象軟件,即更容易實現、更改、 測試和重用。
What problems can the Observer design pattern solve?
觀察者模式解決了以下問題:
- 對象之間的一對多依賴關系應該在不使對象緊密耦合的情況下定義。
- 應該確保當一個對象改變狀態時,自動更新無限數量的依賴對象。
- 一個對象可以通知無限數量的其他對象應該是可能的。
通過定義一個直接更新依賴對象狀態的對象(主體)來定義對象之間的一對多依賴是不靈活的,因為它將主體耦合到特定的依賴對象。盡管如此,從性能的角度來看,或者如果對象實現是緊密耦合的(想想每秒執行數千次的低級內核結構),它仍然有意義。在某些情況下,緊密耦合的對象可能難以實現,并且難以重用,因為它們引用并了解(以及如何更新)具有不同接口的許多不同對象。在其他情況下,緊密耦合的對象可能是更好的選擇,因為編譯器將能夠在編譯時檢測錯誤并在 CPU 指令級別優化代碼。
What solution does the Observer design pattern describe?
定義主題和觀察者對象。
這樣當一個主題改變狀態時,所有注冊的觀察者都會被自動通知和更新(可能是異步的)。
主體的唯一職責是維護觀察者列表并通過調用它們的 update() 操作通知它們狀態變化。 觀察者的職責是在一個主題上注冊(和取消注冊)自己(以獲得狀態變化的通知)并在收到通知時更新他們的狀態(將他們的狀態與主題的狀態同步)。 這使得主體和觀察者松散耦合。 主體和觀察者彼此之間沒有明確的感知。 可以在運行時獨立添加和刪除觀察者。 這種通知-注冊交互也稱為發布-訂閱。
Strong vs. weak reference
觀察者模式會導致內存泄漏,稱為失效偵聽器問題,因為在基本實現中,它需要顯式注冊和顯式取消注冊,就像在處置模式中一樣,因為主體持有對觀察者的強引用,使它們保持活動狀態。 這可以通過主體持有對觀察者的弱引用來防止。
Coupling and typical pub-sub implementations
通常,觀察者模式被實現,因此被“觀察”的“主體”是正在觀察狀態變化的對象的一部分(并傳達給觀察者)。這種類型的實現被認為是“緊密耦合的”,迫使觀察者和主體相互了解并可以訪問它們的內部部分,從而產生可擴展性、速度、消息恢復和維護(也稱為事件或通知)的可能問題損失),條件分散缺乏靈活性,以及??可能妨礙所需的安全措施。在發布-訂閱模式(又名發布-訂閱模式)的一些(非輪詢)實現中,這是通過創建一個專用的“消息隊列”服務器(有時還有一個額外的“消息處理程序”對象)作為額外階段來解決的觀察者和被觀察對象之間,從而解耦組件。在這些情況下,消息隊列服務器由觀察者使用觀察者模式訪問,“訂閱某些消息”只知道預期的消息(或在某些情況下不知道),而對消息發送者本身一無所知;發送者也可能對觀察者一無所知。發布訂閱模式的其他實現,實現了類似的通知和向感興趣的各方通信的效果,根本不使用觀察者模式。
在 OS/2 和 Windows 等多窗口操作系統的早期實現中,術語“發布-訂閱模式”和“事件驅動的軟件開發”被用作觀察者模式的同義詞。
正如 GoF 書中所描述的,觀察者模式是一個非常基本的概念,并沒有解決在通知觀察者之前或之后消除對觀察到的“主體”或被觀察“主體”所做的特殊邏輯的更改的興趣。該模式也不處理發送更改通知時的記錄或保證收到更改通知。這些問題通常在消息隊列系統中處理,其中觀察者模式只是其中的一小部分。
觀察者模式的 UML 和 時序圖
在上面的UML類圖中,Subject類并沒有直接更新依賴對象的狀態。 相反,Subject 引用 Observer 接口(update())來更新狀態,這使得 Subject 獨立于依賴對象的狀態如何更新。 Observer1 和 Observer2 類通過將它們的狀態與主題的狀態同步來實現 Observer 接口。
UML 序列圖顯示了運行時交互:Observer1 和Observer2 對象調用Subject1 上的attach(this) 來注冊它們自己。 假設 Subject1 的狀態發生了變化,Subject1 會對其自身調用 notify() 。
notify() 對已注冊的 Observer1 和 Observer2 對象調用 update(),它們從 Subject1 請求更改的數據 (getState()) 以更新(同步)它們的狀態。
UML 類圖
看一個 Java 的例子。
Subject 即數據源的實現:
import java.util.List; import java.util.ArrayList; import java.util.Scanner;class EventSource {public interface Observer {void update(String event);}private final List<Observer> observers = new ArrayList<>();private void notifyObservers(String event) {observers.forEach(observer -> observer.update(event));}public void addObserver(Observer observer) {observers.add(observer);}public void scanSystemIn() {Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String line = scanner.nextLine();notifyObservers(line);}} }Observer 的實現:
public class ObserverDemo {public static void main(String[] args) {System.out.println("Enter Text: ");EventSource eventSource = new EventSource();eventSource.addObserver(event -> {System.out.println("Received response: " + event);});eventSource.scanSystemIn();} }JavaScript 的實現:
let Subject = {_state: 0,_observers: [],add: function(observer) {this._observers.push(observer);},getState: function() {return this._state;},setState: function(value) {this._state = value;for (let i = 0; i < this._observers.length; i++){this._observers[i].signal(this);}} };let Observer = {signal: function(subject) {let currentValue = subject.getState();console.log(currentValue);} }Subject.add(Observer); Subject.setState(10); //Output in console.log - 10更多Jerry的原創文章,盡在:“汪子熙”:
總結
以上是生活随笔為你收集整理的设计模式中的观察者模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Java 11 安装 SAP Co
- 下一篇: asp.net ajax控件工具集 Au