前言python
有時,咱們但願在一個對象的狀態改變時更新另一組對象。在MVC模式中有這樣一個非 經常見的例子,假設在兩個視圖(例如,一個餅圖和一個電子表格)中使用同一個模型的數據, 不管什麼時候更改了模型,都須要更新兩個視圖。這就是觀察者設計模式要處理的問題(請參考 [Eckel08,第213頁])。 觀察者模式描述單個對象(發佈者,又稱爲主持者或可觀察者)與一個或多個對象(訂閱者, 又稱爲觀察者)之間的發佈 — 訂閱關係。在MVC例子中,發佈者是模型,訂閱者是視圖。然而, MVC並不是是僅有的發佈 — 訂閱例子。信息聚合訂閱(好比,RSS或Atom)是另外一種例子。許多讀 者一般會使用一個信息聚合閱讀器訂閱信息流,每當增長一條新信息時,他們就能自動地獲取到 更新。 觀察者模式背後的思想等同於MVC和關注點分離原則背後的思想,即下降發佈者與訂閱者 之間的耦合度,從而易於在運行時添加/刪除訂閱者。此外,發佈者不關心它的訂閱者是誰。它 只是將通知發送給全部訂閱者(請參考[GOF95,第327頁])。
意圖:定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。設計模式
主要解決:一個對象狀態改變給其餘對象通知的問題,並且要考慮到易用和低耦合,保證高度的協做。網絡
什麼時候使用:一個對象(目標對象)的狀態發生改變,全部的依賴對象(觀察者對象)都將獲得通知,進行廣播通知。app
如何解決:使用面向對象技術,能夠將這種依賴關係弱化。異步
關鍵代碼:在抽象類裏有一個 ArrayList 存放觀察者們。網站
應用實例: 一、拍賣的時候,拍賣師觀察最高標價,而後通知給其餘競價者競價。 二、西遊記裏面悟空請求菩薩降服紅孩兒,菩薩灑了一地水招來一個老烏龜,這個烏龜就是觀察者,他觀察菩薩灑水這個動做。spa
優勢: 一、觀察者和被觀察者是抽象耦合的。 二、創建一套觸發機制。設計
缺點: 一、若是一個被觀察者對象有不少的直接和間接的觀察者的話,將全部的觀察者都通知到會花費不少時間。 二、若是在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。 三、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。3d
使用場景:code
注意事項: 一、JAVA 中已經有了對觀察者模式的支持類。 二、避免循環引用。 三、若是順序執行,某一觀察者錯誤會致使系統卡殼,通常採用異步方式。
當咱們但願在一個對象(主持者/發佈者/可觀察者)發生變化時通知/更新另外一個或多個對象
的時候,一般會使用觀察者模式。觀察者的數量以及誰是觀察者可能會有所不一樣,也能夠(在運
行時)動態地改變。
能夠想到許多觀察者模式在其中有用武之地的案例。本章開頭已提過這樣的一個案例,就是
信息聚合。不管格式爲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()