利用 Observer Pattern - Design Patterns 學習設計模式 Observer Pattern,並利用 Python 撰寫 sample code.
摘要
當有 A 、 B 兩類實體,其中實體 B 類實體為了某些原因,需要得知 A 類的某些狀態是否有改變; 若在 Server-Client 的架構中,假設 Server 為 A 類、Client 為 B 類 ,最簡單的想法是讓 Client 定期去問 Server 狀態是否已經改變,這樣的方式稱為輪詢(Polling)
然而,輪詢有些顯而易見的缺點:
- 大多時候輪詢的答案都是狀態未改變,屬於無效的輪詢
- 若同時有多個 Client 實體對 Server 進行輪詢,會降低 Server 的處理效能
讓 Server 主動**推送(Push)**狀態變更的信號給 Client,可以有效的改善上述的缺點
因此在 Observer Pattern 中,將會明確定義出兩種角色 :
- IObservable : 被觀察者,如上述的 Server (A類)
- IObserver : 觀察者,如上述的 Client (B類)
Pattern
[0..*] 表示 IObservable 和 IObserver 的關係為 1-to-many,
即 1 個 IObservable 可以同時註冊多個 IObserver 。
IObservable
如上圖所述,在 IObservable 介面定義 register(註冊) 、 unregister(註銷) 和 notify(通知) 三種基礎方法。
register
: 註冊 IObserver 。 使得notify()
方法可以通知到已註冊的 Observer。unregister
: 註銷 IObserver 。 已註銷的 IObserver 不會收到 notify 方法的通知notify
: 促使 IObservable 通知目前已註冊且未註銷的 IObserver , 使得符合條件的 IObserver 可以執行update()
IObserver
IObserver 中定義僅定義了 update()
方法,問題是 IObserver 需要和誰取得更新的狀態或訊息 ?
在 IObservable 的描述中,已知 IObservable has-a (or has-many) IObserver , 因此 IObservable 執行 notify()
方法時會有具體的對象可以呼叫, 但 IObserver 卻沒有定義一個可以 update() 的對象。
為此,仍需實做輪詢中的一個重點步驟 : 詢問(Ask) ,同時為了避免 Circular Imports的情形,詢問(Ask) 的實作會放在繼承了 IObserver 的子類建構子中,使得 IObserver 的子類能夠獲取具體向哪一個 IObservable 取得更新的狀態與訊息;
實際上 詢問(Ask) 是一個雙向的溝通,即:有問有答,因此在繼承了 IObservable 的子類中,也會定義對應的方法讓 IObserver 的子類可以獲取更新的狀態和訊息。
Demo
整併上述提及 詢問(Ask) 的實作 ,並定義出 IObservable 和 IObserver 的子類,如下圖所示
用 Python 撰寫 Observer Pattern 的示例。
Source code
# initial publisher and subscribers
topic = Publisher()
sub_1 = Subscriber(publisher=topic)
sub_2 = Subscriber(publisher=topic)
sub_3 = Subscriber(publisher=topic)
# register subscribers to publisher
topic.register(subscriber=sub_1)
topic.register(subscriber=sub_2)
topic.register(subscriber=sub_3)
# set message for publisher
topic.message = 'Demo Observer Pattern'
print(f"{'-'*20}\n")
# change message to verify notify and update
topic.message = 'Change message 2nd times'
print(f"{'-' * 20}\n")
# unregister sub_1
topic.unregister(subscriber=sub_2)
# change message to verify notify and update
topic.message = f'Unregister sub 2 (id: {sub_2.id})'
執行結果
撰文的當下是 2023 年,而 YT 影片大約是 2017 年的產物,以 2023 年應用發展來看,我覺得 Observer Pattern 最常見的一種應用(或者說演化),就是 PubSub Pattern;
因此,我也直接以 Simple Pub/Sub 的概念來實做這個 Demo。