意圖(Intent)#
定義物件之間的一對多依賴關係,當一個物件狀態改變時,所有依賴它的物件都會自動收到通知並更新。
別名(Also Known As)#
- Dependents
- Publish-Subscribe
動機(Motivation)#
將系統拆分為多個協作的類別時,常需要維持相關物件之間的一致性。但不應透過緊密耦合來達成一致性,因為這會降低可重用性。
以試算表與長條圖為例:兩者都能顯示同一份應用程式資料,但彼此不認識對方。當使用者修改試算表中的數據時,長條圖立即反映變更,反之亦然。這意味著兩者都依賴於資料物件,需要在資料變動時收到通知。
Observer 模式的核心角色是 Subject(主題)和 Observer(觀察者)。Subject 可以擁有任意數量的 Observer;當 Subject 狀態變動時,所有 Observer 都會被通知。這種互動也稱為 publish-subscribe:Subject 發布通知,不需要知道觀察者是誰。
適用場景(Applicability)#
- 一個抽象概念有兩個面向,其中一個依賴另一個,將它們封裝在不同物件中可獨立變更與重用
- 一個物件的變更需要連帶改變其他物件,但不確定有多少物件需要改變
- 物件需要通知其他物件,但不希望對這些物件做任何假設(避免緊密耦合)
結構(Structure)#
- Subject 維護一個 Observer 列表,提供 Attach / Detach 介面
- Observer 定義 Update 介面
- ConcreteSubject 持有狀態,狀態變動時呼叫 Notify
- ConcreteObserver 持有對 ConcreteSubject 的參照,透過 Update 同步狀態
classDiagram
class Subject {
+Attach(Observer)
+Detach(Observer)
+Notify()
}
class ConcreteSubject {
-subjectState
+GetState()
+SetState()
}
class Observer {
<<interface>>
+Update()
}
class ConcreteObserver {
-observerState
+Update()
}
Subject <|-- ConcreteSubject
Observer <|.. ConcreteObserver
Subject o--> Observer
ConcreteObserver --> ConcreteSubject參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Subject | - | 知道自己的 Observer,提供附加與解除 Observer 的介面 |
| Observer | - | 定義更新介面,用於接收 Subject 的變更通知 |
| ConcreteSubject | - | 儲存 ConcreteObserver 關心的狀態;狀態改變時通知所有 Observer |
| ConcreteObserver | - | 維護對 ConcreteSubject 的參照;儲存應與 Subject 保持一致的狀態;實作 Observer 的 Update 介面 |
協作方式(Collaborations)#
- ConcreteSubject 在狀態改變時通知所有 Observer
- ConcreteObserver 收到通知後,向 Subject 查詢資訊以同步自身狀態
- 發起變更的 Observer 會延遲自身更新,直到收到 Subject 的通知
sequenceDiagram
participant Client
participant ConcreteSubject
participant ConcreteObserver1
participant ConcreteObserver2
Client->>ConcreteSubject: SetState()
ConcreteSubject->>ConcreteSubject: Notify()
ConcreteSubject->>ConcreteObserver1: Update()
ConcreteObserver1->>ConcreteSubject: GetState()
ConcreteSubject->>ConcreteObserver2: Update()
ConcreteObserver2->>ConcreteSubject: GetState()優缺點(Consequences)#
優點:
- Subject 和 Observer 之間的抽象耦合:Subject 只知道 Observer 的抽象介面,不知道具體類別。兩者可以分屬不同抽象層,低層的 Subject 能通知高層的 Observer,維持系統分層完整性
- 支援廣播通訊:Subject 發出的通知無需指定接收者,自動廣播給所有訂閱者。Observer 可隨時增減,Subject 不受影響
缺點:
- 意外的連鎖更新:Observer 彼此不知道對方的存在,一個看似無害的操作可能引發 Observer 及其依賴物件的連鎖更新。未妥善定義的依賴關係容易導致多餘的更新,且難以追蹤
簡單的 Update 協定不提供「什麼改變了」的資訊。若缺乏額外協定讓 Observer 發現具體變更,Observer 可能需要花費大量資源才能推導出差異。
實作要點(Implementation)#
- Subject 到 Observer 的映射:最簡單的方式是在 Subject 中直接儲存 Observer 列表。若 Subject 數量多但 Observer 少,可用關聯查詢(如 hash table)來節省空間
- 觀察多個 Subject:Observer 可能依賴多個 Subject,此時 Update 介面需讓 Observer 知道是哪個 Subject 發出通知(將 Subject 作為參數傳入)
- 誰觸發更新:
- 由 Subject 的狀態設定操作自動呼叫 Notify——客戶端不需記得呼叫,但連續操作會造成多次更新
- 由客戶端在適當時機呼叫 Notify——可避免不必要的中間更新,但增加客戶端的責任
- 避免懸空參照:Subject 被刪除時應通知 Observer,讓它們重置參照
- 確保通知前狀態一致:呼叫 Notify 前必須確保 Subject 狀態已完全一致。可利用 Template Method 在抽象 Subject 中定義通知流程,確保子類別覆寫時不會在不一致狀態下觸發通知
Push vs. Pull 模型是關鍵設計決策。Push 模型由 Subject 推送詳細變更資訊,Observer 可能收到不需要的資料;Pull 模型由 Observer 主動查詢,Subject 保持對 Observer 的無知,但可能效率較低。
- 指定感興趣的事件:擴展註冊介面讓 Observer 只訂閱特定 aspect(事件),提升更新效率
- ChangeManager:當 Subject 與 Observer 的依賴關係特別複雜時,可引入 ChangeManager 集中管理映射與更新策略。ChangeManager 是 Mediator 模式的實例,通常也是 Singleton
已知應用(Known Uses)#
- Smalltalk MVC:Model 扮演 Subject,View 扮演 Observer,是最早且最知名的 Observer 應用
- ET++、THINK 類別庫:將 Subject 和 Observer 介面放在所有類別的父類別中,提供通用依賴機制
- InterViews:明確定義 Observer 和 Observable 類別
- Andrew Toolkit:使用「view」和「data object」的術語
- Unidraw:將圖形編輯器物件拆分為 View(Observer)和 Subject
相關模式(Related Patterns)#
- Mediator:ChangeManager 封裝複雜的更新語義,扮演 Subject 與 Observer 之間的 Mediator
- Singleton:ChangeManager 通常是全域唯一的,適合使用 Singleton 模式