摘要:在程序設計中,觀察者模式通常被定義為觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態是,它的所有依賴者都會收到通知并自動更新。事件扮演發布者的角色,監聽者則扮演觀察者的角色。
題目:現在你有一個數字,默認格式化程序是以十進制格式展示此數值,但需要提供一個功能,這個程序要支持添加/注冊更多的格式化程序(比如:添加一個十六進制格式化程序和一個二進制格式化程序)。每次數值更新時,已注冊的程序就會收到通知,并顯示更新后的值。
我們看下需求:
NumberFormatter 有一個 number 屬性
當 number 值修改時,相關的格式化方式展示結果要改變
此系統必須可擴展已適應其他格式化方式的使用。
一個錯誤的實現可能是這樣的:
class NumberFormatter(object): def __init__(self, number): self.number = number def show_data(self): self.default_formatter() self.hex_formatter() self.binary_formatter() def default_formatter(self): pass def hex_formatter(self): pass def binary_formatter(self): pass
我們可以這么使用:
number = NumberFormatter(10) number.show_data()
但是這樣會有一個問題:這種針對實現的編程會導致我們在增加或者刪除需要格式化方式時必須修改代碼。比如我們現在不再需要十六進制數字格式的顯示,就需要把 hex_formatter 相關的代碼刪除或者注釋掉。
要解決這個問題,就可以用到我們這次要介紹的觀察者模式了。
什么是觀察者模式 認識觀察者模式我們先看看報紙和雜志的訂閱是怎么回事:
報社的業務就是出版報紙
向某家報社訂閱報紙,只要他們有新報紙,就會給你送來,只要你是他們的訂戶,你就會一直受到新報紙。
當你不再想看的時候,取消訂閱,他們就不會在送新報紙給你
只要報社還在運營,就會一直有人向他們訂閱報紙或取消訂閱。
我們用圖表示一下,這里出版者 改稱為主題(Subject),訂閱者改稱為觀察者(Observer):
1. 開始的時候,鴨子對象不是觀察者
2. 鴨子對象過來告訴主題,它想當一個觀察者(鴨子其實想說的是:我對你的數據改變感興趣,一有變化請通知我)
3. 鴨子對象已經是觀察者了(鴨子靜候通知,一旦接到通知,就會得到一個整數)。
4. 主題有了新的數據(現在鴨子和其他所有觀察者都會受到通知:主題已經改變)
5. 老鼠對象要求從觀察者中把自己除名(老鼠已經觀察次主題太久,決定不再當觀察者了)。
6. 老鼠離開了(主題知道老鼠的請求后,把它從觀察者中移除了)。
7. 主題有了一個新的整數(除了老鼠之外,每個觀察者都會收到通知,如果老鼠又想當觀察者了,它還可以再回來)
當你試圖勾勒觀察者模式時,可以利用報紙訂閱服務,以及出版這和訂閱者比你這一切。在程序設計中,觀察者模式通常被定義為:
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態是,它的所有依賴者都會收到通知并自動更新。
我們和之前的例子做個對比:
主題和觀察者定義了一對多的關系。觀察者依賴于此主題,只要主題狀態一有變化,觀察者就會被通知。根據通知的風格,觀察者可能因此新值而更新。
現在你可能有疑問,這和一對多的關系有何關聯?
利用觀察者模式,主題是具有狀態的對象,并且可以控制這些狀態。也就是說,有一個具有狀態的主題。另一方面,觀察者使用這些狀態,雖然這些狀態不屬于他們。有許多觀察者,依賴主題告訴他們狀態何時改變了。這就產生了一個關系:一個主題對多個觀察者的關系。
觀察者和主題之間的依賴關系是如何產生的?
主題是真正擁有數據的人,觀察者是主題的依賴者,在數據變化時更新,這樣比起讓許多對象控制同一份數據來,可以得到更干凈的 OO 設計。觀察者模式的應用案例
觀察者模式在實際應用中有許多的案例,比如信息的聚合。無論格式為 RSS、Atom 還是其它,思想多事一樣的:你追隨某個信息源,當它每次更新時,你都會收到關于更新的通知。
事件驅動系統是一個可以使用觀察者模式的例子。在這種系統中,監聽者被用于監聽特定的事件。監聽者的事件被創建出來時就會觸發它們。這個事件可以使鍵入某個特定的鍵、移動鼠標或者其他。事件扮演發布者的角色,監聽者則扮演觀察者的角色。
現在,讓我們回到文章開始的那個問題。
這里我們可以實現一個基類 Publisher,包括添加、刪除及通知觀察者這些公用功能。DefaultFormatter 類繼承自 Publisher,并添加格式化程序特定的功能。
Publisher 的代碼如下:
import itertools """ 觀察者模式實現 """ class Publisher: def __init__(self): self.observers = set() def add(self, observer, *observers): for observer in itertools.chain((observer, ), observers): self.observers.add(observer) observer.update(self) def remove(self, observer): try: self.observers.discard(observer) except ValueError: print("Failed to remove: {}".format(observer)) def notify(self): [observer.update(self) for observer in self.observers]
現在,打算使用觀察者模式的模型或類都應該繼承 Publisher 類。該類用 set 來保存觀察者對象。當用戶向 Publisher 注冊新的觀察者對象時,觀察者的 update() 方法會執行,這使得它能夠用模型當前的狀態初始化自己。模型狀態發生變化時,應該調用繼承而來的 notify() 方法,這樣的話,就會執行每個觀察者對象的 update() 方法,以確保他們都能反映出模型的最新狀態。
add() 方法的寫法值得注意,這里是為了支持可以接受一個或多個觀察者對象。這里我們采用了itertools.chain() 方法,它可以接受任意數量的 iterable,并返回單個iterable。遍歷這個 iterable,也就相當于依次遍歷參數里的那些 iterable。
接下來是 DefaultFomatter 類。__init__() 做的第一件事就是調用基類的__init__() 方法,因為這在 Python 中沒法自動完成。DefaultFormatter 實例有自己的名字,這樣便于我們跟蹤其狀態。對于_data 變量,我們使用了名稱改編來聲明不能直接訪問該變量。DefaultFormatter 把_data 變量用作一個整數,默認值為0。
class DefaultFormatter(Publisher): def __init__(self, name): Publisher.__init__(self) self.name = name self._data = 0 def __str__(self): return "{}: "{}" has data = {}".format(type(self).__name__, self.name, self._data) @property def data(self): return self._data @data.setter def data(self, new_value): try: self._data = int(new_value) except ValueError as e: print("Error: {}".format(e)) else: self.notify()
__str__() 方法返回關于發布者名稱和 _data 值的信息。type(self).__name 是一種獲取類名的方便技巧,避免硬編碼類名。(不過這會降低代碼的可讀性)
data() 方法有兩個,第一個使用了 @property 裝飾器來提供_data 變量的讀訪問方式。這樣,我們就能使用 object.data 來代替 object._data。第二個 data() 方法使用了@setter 裝飾器,改裝飾器會在每次使用賦值操作符(=)為_data 變量賦值時被調用。該方法也會嘗試把新值強制轉換為一個整數,并在轉換失敗時處理異常。
接下來是添加觀察者。HexFormatter 和 BinaryFormatter 功能基本相似。唯一的不同在于如何格式化從發布者那獲取到的數據值,即十六進制和二進制格式化。
class HexFormatter: def update(self, publisher): print("{}: "{}" has now hex data= {}".format(type(self).__name__, publisher.name, hex(publisher.data))) class BinaryFormatter: def update(self, publisher): print("{}: "{}" has now bin data= {}".format(type(self).__name__, publisher.name, bin(publisher.data)))
接下來我們添加一下測試數據,運行代碼觀察一下結果:
def main(): df = DefaultFormatter("test1") print(df) print() hf = HexFormatter() df.add(hf) df.data = 3 print(df) print() bf = BinaryFormatter() df.add(bf) df.data = 21 print(df) print() df.remove(hf) df.data = 40 print(df) print() df.remove(hf) df.add(bf) df.data = "hello" print(df) print() df.data = 4.2 print(df) if __name__ == "__main__": main()
完整代碼參考:https://gist.github.com/gusibi/93a000c79f3d943dd58dcd39c4b547f1
運行代碼:
python observer.py ## output DefaultFormatter: "test1" has data = 0 HexFormatter: "test1" has now hex data= 0x0 HexFormatter: "test1" has now hex data= 0x3 DefaultFormatter: "test1" has data = 3 BinaryFormatter: "test1" has now bin data= 0b11 BinaryFormatter: "test1" has now bin data= 0b10101 HexFormatter: "test1" has now hex data= 0x15 DefaultFormatter: "test1" has data = 21 BinaryFormatter: "test1" has now bin data= 0b101000 DefaultFormatter: "test1" has data = 40 BinaryFormatter: "test1" has now bin data= 0b101000 Error: invalid literal for int() with base 10: "hello" DefaultFormatter: "test1" has data = 40 BinaryFormatter: "test1" has now bin data= 0b100 DefaultFormatter: "test1" has data = 4
在輸出中我們看到,添加額外的觀察者,就會出現更多的輸出;一個觀察者被刪除后就不再被通知到。
總結這一篇我們介紹了觀察者模式的原理以及 Python 代碼的實現。在實際的項目開發中,觀察者模式廣泛的運用于 GUI 編程,而且在仿真及服務器等其他時間處理架構中也能用到,比如:數據庫觸發器、Django 的信號系統、Qt GUI 應用程序框架的信號(signal)與槽(slot)機智以及WebSocket的許多用例。
參考鏈接The 10 Minute Guide to the Observer Pattern in Python
Observer
最后,感謝女朋友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/40864.html
摘要:監聽模式又名觀察者模式發布訂閱模式源監聽器模式,模式的核心是設計時要區分誰是被觀察者,誰是觀察者。 監聽模式 又名觀察者模式、發布/訂閱模式、源-監聽器(Source/Listener)模式,模式的核心是:設計時要區分誰是被觀察者,誰是觀察者。被觀察者至少有三個方法,添加觀察者、刪除觀察者、監聽目標變化并通知觀察者;觀察者這至少包含一個方法,當接收到被觀察者的通知時,做出相應的處理(即...
摘要:所以,以后,只有甲乙會收到消息。原理當我們遇到一個多對一的依賴關系時,就可以用觀察者模式。觀察者模式有一個被觀察者和多個觀察者。被觀察者提供注冊刪除通知的功能,觀察者提供數據更新和展示等功能。 1.白話栗子 市里新修了一個圖書館,現在招募一個圖書管理員叫T,T知道圖書館里的圖書更新和借閱等信息。現在有三個同學甲乙丙想去了解以后幾個月的圖書館圖書信息和借閱信息,于是它們去T那里注冊登記。...
摘要:在本節實驗中,我們學習了四種設計模式策略模式,觀察者模式,命令模式以及模板方法模式。這四種設計模式都是行為型模式。這就是適配器模式。下面讓我們看看適配器模式在實驗樓中使用吧。準確來說,裝飾者模式能動態的給對象添加行為。 1、策略模式 策略模式將各種操作(算法)進行封裝,并使它們之間可以互換。互換的意思是說可以動態改變對象的操作方式(算法)。 -- coding: utf-8 -- im...
摘要:借助繼承為對象安裝發布訂閱功能根據自己需求定義一個函數供事件處理完后調用創建個回調函數訂閱和這個事件,并且綁定相關的完成后的函數當兩個事件完成時候,觸發前幾行綁定的相關函數打印實現中一般用事件模型來代替傳統的發布訂閱模式。 博主按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前采用javascript(_靠這吃飯_)和python(_純粹喜歡_)兩種語言實現。誠然,每種設計模式都...
摘要:借助繼承為對象安裝發布訂閱功能根據自己需求定義一個函數供事件處理完后調用創建個回調函數訂閱和這個事件,并且綁定相關的完成后的函數當兩個事件完成時候,觸發前幾行綁定的相關函數打印實現中一般用事件模型來代替傳統的發布訂閱模式。 博主按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前采用javascript(_靠這吃飯_)和python(_純粹喜歡_)兩種語言實現。誠然,每種設計模式都...
閱讀 2440·2021-11-22 13:53
閱讀 1134·2021-09-22 16:06
閱讀 1376·2021-09-02 15:21
閱讀 1907·2019-08-30 15:55
閱讀 3127·2019-08-29 11:19
閱讀 1925·2019-08-26 13:23
閱讀 944·2019-08-23 18:23
閱讀 1760·2019-08-23 16:06