python設計模式之觀察者模式
說到觀察者模式,在我腦海中總是閃現,這傢伙跟消息隊列的主題發布訂閱有什麼關係,雖然本人對消息隊列沒有很深的研究,但是憑直覺我就認為消息隊列的實現就使用了觀察者模式吧,所以本文就來模擬消息隊列的丐版實現闡述觀察者模式是怎樣玩的。
觀察者模式的GOF官方解釋是: 定義對象間的一種一對多(變化)的依賴關係, 以便當一個對象(Subject)的狀態發生改變時,所有依賴於它的對象都得到通知並更新。
觀察者模式類圖如下:
主要構成就是主題基類, 觀察者基類及其他們的實現。接下來我們開始設計屬於我們自己的消息隊列。
01、 首先設計主題基類
from abc import ABC class Subject(ABC): def __init__(self): self.observers = list() def add_observer(self, observer): self.observers.append(observer) def pop_observer(self, observer): self.observers.remove(observer) def notify(self): for observer in self.observers: observer.update()
在Subject基類中,我們需要定義一個觀察者列表用於盛放觀察者對象,然後我們需要有添加和刪除觀察者的方法,最後一個必要的方法就是通知觀察者更新。
02、設計我們的主題子類
class GameSubject(Subject): def notify(self, msg): for observer in self.observers: observer.update(msg)
我們設計一個遊戲主題類,專門給觀察者推送遊戲消息,所以我們重寫了nitify方法。
03、設計我們的觀察者子類
from queue import Queue class Observer: def __init__(self): self.queue = Queue(100) def update(self): pass
在觀察者基類中,我們定一個隊列用於接收主題發布的消息, 還有聲明一個更新方法,用於給子類繼承。
04、設計我們的觀察者子類
class LolObserver(Observer): def __init__(self, name): self.name = name super().__init__() def update(self, msg): self.queue.put(msg) def get_msg(self): while not self.queue.empty(): msg = self.queue.get() print(self.name + "正在讀取消息:" + msg) class DNFObserver(Observer): def __init__(self, name): self.name = name super().__init__() def update(self, msg): self.queue.put(msg) def get_msg(self): while not self.queue.empty(): msg = self.queue.get() print(self.name + "正在讀取消息:" + msg)
在我們的觀察者子類中,我們主要定義了,對隊列的IO操作。
05、 主程式
if __name__ == "__main__": game_subject = GameSubject() lol_observer = LolObserver('lol選手') dnf_observer = DNFObserver("DNF選手") game_subject.add_observer(lol_observer) game_subject.add_observer(dnf_observer) game_subject.notify("第一屆遊戲大賽正在開始") game_subject.notify("我們友情兩大遊戲金牌選手") game_subject.notify("誰會是第一名呢?") game_subject.pop_observer(dnf_observer) game_subject.notify("會是LOL選手嗎?") game_subject.notify("貌似DNF選手掉線了啊") game_subject.add_observer(dnf_observer) game_subject.notify("我們的選手又回來了") lol_observer.get_msg() print("="*77) dnf_observer.get_msg()
運行結果如下:
/usr/local/bin/python3.7 /Users/bytedance/PycharmProjects/untitled3/模版設計模式/觀察者模式.py lol選手正在讀取消息:第一屆遊戲大賽正在開始 lol選手正在讀取消息:我們友情兩大遊戲金牌選手 lol選手正在讀取消息:誰會是第一名呢? lol選手正在讀取消息:會是LOL選手嗎? lol選手正在讀取消息:貌似DNF選手掉線了啊 lol選手正在讀取消息:我們的選手又回來了 ============================================================================= DNF選手正在讀取消息:第一屆遊戲大賽正在開始 DNF選手正在讀取消息:我們友情兩大遊戲金牌選手 DNF選手正在讀取消息:誰會是第一名呢? DNF選手正在讀取消息:我們的選手又回來了 Process finished with exit code 0
到此我們的丐版的消息隊列就完成了,哈哈,以此類推諸如微信群功能,都可以用此模式去實現,到此感覺觀察者模式不像是一種模式,更像是一種業務的實現的技巧,但是它的關鍵點在於,你在遍歷觀察者列表處的巧妙,利用面向對象多態的特性,只要你繼承自觀察者基類,都可以調用update方法,無須是具體的觀察者。
07、總結
- 觀察者設計模式使得我們可以獨立地改變主題和觀察者,從而使二者的依賴關係達到松耦合的目的。
- 目標發送通知時,我們無須指定接收者,消息自動傳播到接受者處。
- 但是當觀察者過多時可能會產生性能問題,因為我們是在遍歷觀察者列表
最後還是奉上我們的設計模式八大設計原則:
- 依賴倒置原則(DIP)
- 高層模組(穩定)不應該依賴於低層模組(變化),二者都應該依賴於抽象(穩定) 。
- 抽象(穩定)不應該依賴於實現細節(變化) ,實現細節應該依賴於抽象(穩定)。
- 開放封閉原則(OCP)
- 對擴展開放,對更改封閉。
- 類模組應該是可擴展的,但是不可修改。
- 單一職責原則(SRP)
- 一個類應該僅有一個引起它變化的原因。
- 變化的方向隱含著類的責任。
- Liskov 替換原則(LSP)
- 子類必須能夠替換它們的基類(IS-A)。
- 繼承表達類型抽象。
- 介面隔離原則(ISP)
- 不應該強迫客戶程式依賴它們不用的方法。
- 介面應該小而完備。
- 優先使用對象組合,而不是類繼承
- 類繼承通常為「白箱復用」,對象組合通常為「黑箱復用」 。
- 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
- 而對象組合則只要求被組合的對象具有良好定義的介面,耦合度低。
- 封裝變化點
- 使用封裝來創建對象之間的分界層,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響,從而實現層次間的松耦合。
- 針對介面編程,而不是針對實現編程
- 不將變數類型聲明為某個特定的具體類,而是聲明為某個介面。
- 客戶程式無需獲知對象的具體類型,只需要知道對象所具有的介面。
- 減少系統中各部分的依賴關係,從而實現「高內聚、松耦合」的類型設計方案