觀察者模式
定義
定義物件間的一種一對多的依賴關係 ,當一個物件的狀態發生改變時 , 所有依賴於它的物件都得到通知並被自動更新。
動機
將一個系統分割成一系列相互協作的類有一個常見的副作用:需要維護相關物件間的一致性。我們不希望為了維持一致性而使各類緊密耦合,因為這樣降低了它們的可重用性。
適用性
- 當一個抽象模型有兩個方面 , 其中一個方面依賴於另一方面。將這二者封裝在獨立的物件中以使它們可以各自獨立地改變和複用。
- 當對一個物件的改變需要同時改變其它物件 , 而不知道具體有多少物件有待改變。
- 當一個物件必須通知其它物件,而它又不能假定其它物件是誰。換言之 , 你不希望這些物件是緊密耦合的。
優缺點
- 目標和觀察者間的抽象耦合
- 支援廣播通訊
- 意外的更新
實現
有一個氣象站可以獲取溫度、溼度、氧氣的資料,和一些皮膚,每當資料更新時候要顯示在皮膚上 —— 《Head First 設計模式》
class AbstractObservable(object):
def register(self):
raise NotImplementedError(
'register is a abstract method which must be implemente')
def remove(self):
raise NotImplementedError(
'remove is a abstract method which must be implemente')複製程式碼
觀察者這觀察的物件,稱作可觀察物件,該抽象類需要實現具體的註冊和刪除觀察者管理方法。
class AbstractDisplay(object):
def update(self):
raise NotImplementedError(
'update is a abstract method which must be implemente')
def display(self):
raise NotImplementedError(
'display is a abstract method which must be implemente')複製程式碼
觀察者抽象類,需要實現 update 方法,讓可觀察物件可以通知觀察者。
class Subject(object):
def __init__(self, subject):
self.subject = subject
self._observers = []
def register(self, ob):
self._observers.append(ob)
def remove(self, ob):
self._observers.remove(ob)
def notify(self, data=None):
for ob in self._observers:
ob.update(data)複製程式碼
此外還實現了一個 Subject 用於管理多個事件的通知,可以稱作可觀察物件管理者。
class WeatherData(AbstractObservable):
def __init__(self, *namespaces):
self._nss = {}
self._clock = None
self._temperature = None
self._humidity = None
self._oxygen = None
for ns in namespaces:
self._nss[ns] = Subject(ns)
def register(self, ns, ob):
if ns not in self._nss:
raise Exception('this {} is invalid namespace'.format(ns))
self._nss[ns].register(ob)
def remove(self, ns, ob):
return self._nss[ns].remove(ob)
def set_measurement(self, data):
# 此處實現可以更加緊湊,但是為了表達更簡單,採用如下方式
self._clock = data['clock']
self._temperature = data['temperature']
self._humidity = data['humidity']
self._oxygen = data['oxygen']
for k in self._nss.keys():
if k != 'all':
data = self
self._nss[k].notify(data)
# 以下 property 為了實現 pull 模式
@property
def clock(self):
return self._clock
@property
def temperature(self):
return self._temperature
@property
def humidity(self):
return self._humidity
@property
def oxygen(self):
return self._oxygen複製程式碼
觀察者模式的可觀察物件實現可以分成兩種實現方案:
- push 模式
- pull 模式
push 模式能保證所有的觀察者可以接收到全部的資料,無論需要不需要,頻繁更新會影響效能。
pull 模式需要觀察者自己拉去資料,實現起來比較容易出錯,但是能按需獲取資訊。
class OverviewDisplay(AbstractDisplay):
def __init__(self):
self._data = {}
def update(self, data):
self._data = data
self.display()
def display(self):
print(u'總覽顯示皮膚:')
for k, v in self._data.items():
print(k + ': ' + str(v))複製程式碼
這是一個總覽的 Display ,採用 push 模式更新,獲取當前能獲取的所有資料,並且顯示出來。
class TemperatureDisplay(AbstractDisplay):
def __init__(self):
self._storage = []
def update(self, data):
dt = data.clock
temperature = data.temperature
self._storage.append((dt, temperature))
self.display()
def display(self):
print(u'溫度顯示皮膚:')
for storey in self._storage:
print(storey[0] + ': ' + str(storey[1]))複製程式碼
一個只會顯示溫度的 Display,能觀察到時間和溫度變化,由於只關心溫度資料,所以採用 pull 模式更加合適。
if __name__ == '__main__':
import time
# 生成一個可觀察物件,支援('all', 'temperature', 'humidity', 'oxygen')的資料通知
wd = WeatherData('all', 'temperature', 'humidity', 'oxygen')
# 兩個觀察者物件
od = OverviewDisplay()
td = TemperatureDisplay()
# 註冊到可觀察物件中,能獲取資料更新
wd.register('all', od)
wd.register('temperature', td)
# 更新資料,可觀察物件將會自動更新資料
wd.set_measurement({
'clock': time.strftime("%Y-%m-%d %X", time.localtime()),
'temperature': 20,
'humidity': 60,
'oxygen': 10
})
# 一秒後再次更新資料
time.sleep(1)
print('\n')
wd.set_measurement({
'clock': time.strftime("%Y-%m-%d %X", time.localtime()),
'temperature': 21,
'humidity': 58,
'oxygen': 7
})複製程式碼
執行的結果如下:
總覽顯示皮膚:
humidity: 60
temperature: 20
oxygen: 10
clock: 2017-03-26 18:08:41
溫度顯示皮膚:
2017-03-26 18:08:41: 20
總覽顯示皮膚:
humidity: 58
temperature: 21
oxygen: 7
clock: 2017-03-26 18:08:42
溫度顯示皮膚:
2017-03-26 18:08:41: 20
2017-03-26 18:08:42: 21複製程式碼
一秒後資料更新,兩個皮膚會自動更新資料。
Python 設計模式相關程式碼可以 github.com/zhengxiaowa… 獲得。
該模式的程式碼可以從 raw.githubusercontent.com/zhengxiaowa… 獲得
當需要一個溼度皮膚時候也是隻需要生成這個皮膚、並且實現你所需要的 update、display 方法然後再註冊到可觀察物件中即可,無須修改其他部分,實現了結構的解耦。
觀察者模式在很多軟體和框架中經常出現,比如 MVC 框架,事件的迴圈等應用場景。若希望在一個物件的狀態變化時能夠通知/提醒所有相關者(一個物件或一組物件),則可以使用觀察者模式。觀察者模式的一個重要特性是,在執行時,訂閱者/觀察者的數量以及觀察者是誰可能會變化,也可以改變。