觀察者模式是一種軟體設計模式,其中一個名為主體(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()) 以更新(同步)它們的狀态。
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