說到觀察者模式,在我腦海中總是閃現,這傢伙跟訊息佇列的主題釋出訂閱有什麼關係,雖然本人對訊息佇列沒有很深的研究,但是憑直覺我就認為訊息佇列的實現就使用了觀察者模式吧,所以本文就來模擬訊息佇列的丐版實現闡述觀察者模式是怎樣玩的。
觀察者模式的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)
- 不應該強迫客戶程式依賴它們不用的方法。
- 介面應該小而完備。
- 優先使用物件組合,而不是類繼承
- 類繼承通常為“白箱複用”,物件組合通常為“黑箱複用” 。
- 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
- 而物件組合則只要求被組合的物件具有良好定義的介面,耦合度低。
- 封裝變化點
- 使用封裝來建立物件之間的分界層,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響,從而實現層次間的鬆耦合。
- 針對介面程式設計,而不是針對實現程式設計
- 不將變數型別宣告為某個特定的具體類,而是宣告為某個介面。
- 客戶程式無需獲知物件的具體型別,只需要知道物件所具有的介面。
- 減少系統中各部分的依賴關係,從而實現“高內聚、鬆耦合”的型別設計方案