python設計模式之觀察者模式

程式碼一字狂 發表於 2020-06-30

  說到觀察者模式,在我腦海中總是閃現,這傢伙跟訊息佇列的主題釋出訂閱有什麼關係,雖然本人對訊息佇列沒有很深的研究,但是憑直覺我就認為訊息佇列的實現就使用了觀察者模式吧,所以本文就來模擬訊息佇列的丐版實現闡述觀察者模式是怎樣玩的。

觀察者模式的GOF官方解釋是: 定義物件間的一種一對多(變化)的依賴關係, 以便當一個物件(Subject)的狀態發生改變時,所有依賴於它的物件都得到通知並更新。

  觀察者模式類圖如下:

python設計模式之觀察者模式

 

  主要構成就是主題基類, 觀察者基類及其他們的實現。接下來我們開始設計屬於我們自己的訊息佇列。

 

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、總結

  1. 觀察者設計模式使得我們可以獨立地改變主題和觀察者,從而使二者的依賴關係達到鬆耦合的目的。
  2. 目標傳送通知時,我們無須指定接收者,訊息自動傳播到接受者處。
  3. 但是當觀察者過多時可能會產生效能問題,因為我們是在遍歷觀察者列表

 

最後還是奉上我們的設計模式八大設計原則:

  1. 依賴倒置原則(DIP)
  • 高層模組(穩定)不應該依賴於低層模組(變化),二者都應該依賴於抽象(穩定) 。
  • 抽象(穩定)不應該依賴於實現細節(變化) ,實現細節應該依賴於抽象(穩定)。
  1. 開放封閉原則(OCP)
  • 對擴充套件開放,對更改封閉。
  • 類模組應該是可擴充套件的,但是不可修改。
  1. 單一職責原則(SRP)
  • 一個類應該僅有一個引起它變化的原因。
  • 變化的方向隱含著類的責任。
  1. Liskov 替換原則(LSP)
  • 子類必須能夠替換它們的基類(IS-A)。
  • 繼承表達型別抽象。
  1. 介面隔離原則(ISP)
  • 不應該強迫客戶程式依賴它們不用的方法。
  • 介面應該小而完備。
  1. 優先使用物件組合,而不是類繼承
  • 類繼承通常為“白箱複用”,物件組合通常為“黑箱複用” 。
  • 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
  • 而物件組合則只要求被組合的物件具有良好定義的介面,耦合度低。
  1. 封裝變化點
  • 使用封裝來建立物件之間的分界層,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響,從而實現層次間的鬆耦合。
  1. 針對介面程式設計,而不是針對實現程式設計
  • 不將變數型別宣告為某個特定的具體類,而是宣告為某個介面。
  • 客戶程式無需獲知物件的具體型別,只需要知道物件所具有的介面。
  • 減少系統中各部分的依賴關係,從而實現“高內聚、鬆耦合”的型別設計方案