題目
:如今你有一個數字,默認格式化程序是以十進制格式展現此數值,但須要提供一個功能,這個程序要支持添加/註冊更多的格式化程序(好比:添加一個十六進制格式化程序和一個二進制格式化程序)。每次數值更新時,已註冊的程序就會收到通知,並顯示更新後的值。html
咱們看下需求:python
一個錯誤的實現多是這樣的:git
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複製代碼
咱們能夠這麼使用:github
number = NumberFormatter(10)
number.show_data()複製代碼
可是這樣會有一個問題:這種針對實現的編程會致使咱們在增長或者刪除須要格式化方式時必須修改代碼。
好比咱們如今再也不須要十六進制數字格式的顯示,就須要把 hex_formatter
相關的代碼刪除或者註釋掉。shell
要解決這個問題,就能夠用到咱們此次要介紹的觀察者模式
了。數據庫
咱們先看看報紙和雜誌的訂閱是怎麼回事:編程
咱們用圖表示一下,這裏出版者
改稱爲主題(Subject)
,訂閱者
改稱爲觀察者(Observer)
:bash
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()複製代碼
完整代碼參考:gist.github.com/gusibi/93a0…
運行代碼:
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
的許多用例。
最後,感謝女友支持。
歡迎關注(April_Louisa) | 請我喝芬達 |
---|---|