python實現觀察者模式
?
?
前言
有時,我們希望在一個對象的狀態改變時更新另外一組對象。在MVC模式中有這樣一個非 常常見的例子,假設在兩個視圖(例如,一個餅圖和一個電子表格)中使用同一個模型的數據, 無論何時更改了模型,都需要更新兩個視圖。這就是觀察者設計模式要處理的問題(請參考 [Eckel08,第213頁])。 觀察者模式描述單個對象(發布者,又稱為主持者或可觀察者)與一個或多個對象(訂閱者, 又稱為觀察者)之間的發布 — 訂閱關系。在MVC例子中,發布者是模型,訂閱者是視圖。然而, MVC并非是僅有的發布 — 訂閱例子。信息聚合訂閱(比如,RSS或Atom)是另一種例子。許多讀 者通常會使用一個信息聚合閱讀器訂閱信息流,每當增加一條新信息時,他們就能自動地獲取到 更新。 觀察者模式背后的思想等同于MVC和關注點分離原則背后的思想,即降低發布者與訂閱者 之間的耦合度,從而易于在運行時添加 / 刪除訂閱者。此外,發布者不關心它的訂閱者是誰。它 只是將通知發送給所有訂閱者(請參考[GOF95,第327頁])。
?
介紹
意圖: 定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。
主要解決: 一個對象狀態改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協作。
何時使用: 一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。
如何解決: 使用面向對象技術,可以將這種依賴關系弱化。
關鍵代碼: 在抽象類里有一個 ArrayList 存放觀察者們。
應用實例: ?1、拍賣的時候,拍賣師觀察最高標價,然后通知給其他競價者競價。 2、西游記里面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜,這個烏龜就是觀察者,他觀察菩薩灑水這個動作。
優點: ?1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發機制。
缺點: ?1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。 2、如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。 3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
使用場景:
- 一個抽象模型有兩個方面,其中一個方面依賴于另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
- 一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
- 一個對象必須通知其他對象,而并不知道這些對象是誰。
- 需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
注意事項: ?1、JAVA 中已經有了對觀察者模式的支持類。 2、避免循環引用。 3、如果順序執行,某一觀察者錯誤會導致系統卡殼,一般采用異步方式。
?
?
應用案例
當我們希望在一個對象(主持者/發布者/可觀察者)發生變化時通知/更新另一個或多個對象
的時候,通常會使用觀察者模式。觀察者的數量以及誰是觀察者可能會有所不同,也可以(在運
行時)動態地改變。
可以想到許多觀察者模式在其中有用武之地的案例。本章開頭已提過這樣的一個案例,就是
信息聚合。無論格式為RSS、Atom還是其他,思想都一樣:你追隨某個信息源,當它每次更新時,
你都會收到關于更新的一個通知(請參考[Zlobin13,第60頁])。
同樣的概念也存在于社交網絡。如果你使用社交網絡服務關聯了另一個人,在關聯的人更新
某些內容時,你能收到相關通知,不論這個關聯的人是你關注的一個Twitter用戶,Facebook上的
一個真實朋友,還是LinkdIn上的一位同事。
事件驅動系統是另一個可以使用(通常也會使用)觀察者模式的例子。在這種系統中,監聽
者被用于監聽特定事件。監聽者正在監聽的事件被創建出來時,就會觸發它們。這個事件可以是
鍵入(鍵盤的)某個特定鍵、移動鼠標或者其他。事件扮演發布者的角色,監聽者則扮演觀察者
的角色。在這里,關鍵點是單個事件(發布者)可以關聯多個監聽者(觀察者),請參考網頁
[t.cn/Rqr1Xgj]。
?
實現
我們將實現一個數據格式化程序。這里描述的想法來源于ActiveState網站上觀察者
模式用法的Python代碼實現(請參考網頁[t.cn/Rqr1SDO])。默認格式化程序是以十進制格式展
示一個數值。然而,我們可以添加/注冊更多的格式化程序。這個例子中將添加一個十六進制格
式化程序和一個二進制格式化程序。每次更新默認格式化程序的值時,已注冊的格式化程序就會
收到通知,并采取行動。在這里,行動就是以相關的格式展示新的值。
在一些模式中,繼承能體現自身價值,觀察者模式是這些模式中的一個。我們可以實現一個
基類 Publisher ,包括添加、刪除及通知觀察者這些公用功能。 DefaultFormatter 類繼承自
Publisher ,并添加格式化程序特定的功能。我們可以按需動態地添加刪除觀察者。下面的類圖
展示了一個使用兩個觀察者( HexFormatter 和 BinaryFormatter )的示例。注意,因為類圖
是靜態的,所以無法展示系統的整個生命周期,只能展示某個特定時間點的系統狀態。
?
?
# observers存儲觀察者 # add()方法注冊一個新的觀察者 # remove()方法注銷一個已有的觀察者 # notify() 方法則在變化發生時通知所有觀察者。 class Publisher: def __init__ (self): self.observers = [] def add(self, observer): if observer not in self.observers: self.observers.append(observer) else : print ( ' Failed to add: {} ' .format(observer)) def remove(self, observer): try : self.observers.remove(observer) except ValueError: print ( ' Failed to remove: {} ' .format(observer)) def notify(self): [o.notify(self) for o in self.observers] # __str__() 方法返回關于發布者名稱和 _data 值的信息。 # 第一個使用 @property 修飾器來提供 _data 變量的讀訪問方式。這樣,我們就能使用 object.data 來替代 object.data() 。 # 第二個 data() 更有意思。它使用了 @setter 修飾器,該修飾器會在每次使用賦值操作符( = ) 為 _data 變量賦新值時被調用。 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 : print ( " 修改了 " ) self.notify() # 下一步是添加觀察者。 HexFormatter 和 BinaryFormatter 的功能非常相似。唯一的不同 # 在于如何格式化從發布者那獲取到的數據值,即分別以十六進制和二進制進行格式化。 class HexFormatter: def notify(self, publisher): print ( " {}: '{}' has now hex data = {} " .format(type(self). __name__ , publisher.name, hex(publisher.data))) class BinaryFormatter: def notify(self, publisher): print ( " {}: '{}' has now bin data = {} " .format(type(self). __name__ , publisher.name, bin(publisher.data))) def main(): df = DefaultFormatter( ' test1 ' ) hf = HexFormatter() bf = BinaryFormatter() # 添加觀察者 df.add(hf) df.add(bf) # 每次修改發布者的值,observers里面的觀察者會調用notify()方法 df.data = 1 df.data = 10 # 當把某個觀察者踢出observers,當修改發布者的data值就不會通知這個觀察者 df.remove(hf) df.data = 6 if __name__ == ' __main__ ' : main()
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
