設計模式(Python)-觀察者模式

本系列文章是希望將軟件項目中最常見的設計模式用通俗易懂的語言來講解清楚,并通過Python來實現,每個設計模式都是圍繞如下三個問題:

  1. 為什么?即為什么要使用這個設計模式,在使用這個模式之前存在什么樣的問題?
  2. 是什么?通過Python語言來去實現這個設計模式,用于解決為什么中提到的問題。
  3. 怎么用?理解了為什么我們也就基本了解了什么情況下使用這個模式,不過在這里還是會細化使用場景,闡述模式的局限和優缺點。

一些基本概念

在開始講本次設計模式之前,我們先搞清楚軟件設計中的一些基本概念吧。軟件設計中我們有一個常用的概念叫“耦合”,我們常常說模塊之間要“解耦合”,軟件設計要“松耦合”。那么什么是“耦合”呢?
實際上耦合是指兩個模塊之間(模塊可以理解為兩坨代碼,所以可以是函數,可以是類,或者更大范圍的系統)發生了關系,比如一個模塊調用了另一個模塊的函數,或者一個模塊需要獲取另一個模塊的數據,總之有了聯系就有了耦合。所謂“解耦合”或者“松耦合”是說讓兩個模塊之間的聯系沒有那么緊密,這樣一個模塊的變化不會影響另一個模塊的代碼。那么我們如何來使我們的系統是一個“松耦合”的系統呢?
實現“松耦合”最重要的就是抽象一致性,白話就是要通過抽象的接口(Python中沒有接口的概念,可以理解為抽象的類)來聯系調用方和被調方。
一般的,假設我們實現了兩個類A和B,我們可能會如下調用

classcall.jpg

代碼可能是這樣的

class A(object):
    def __init__(self):
        self.obj = B(param1, param2)

    def method_of_a(self):
        self.obj.method_of_b(param3)


class B(object):
    def __init__(self, param1, param2):
        ...

    def method_of_b(self, param3):
         # do something
        ....

但是這種情況下,如果B發生改變,或者想換成另一個類C,這時就需要更改A的代碼,那么如果我們把A和B之間的聯系抽象出來,通過接口(或者類似接口的東西)來連接A和B我們就可以某種程度上屏蔽這種變化,如下圖所示:

interface.jpg

可能的代碼如下:

class A(object):
    def __init__(self, some_obj):
        self.obj = some_obj

    def method_of_a(self):
        self.obj.consistent_method(param3)

class B(object):
    def __init__(self, param1, param2):
        ...
    def consistent_method(self, param3):
        ...

class C(object):
    def __init__(self, param1, param2):
        ...
    def consistent_method(self, param3):
        ...

可以看出由于Python對參數沒有類型約束,所以天然的支持接口,這也是Python靈活的地方之一。在這個例子中,我們的參數some_obj可以想象成一個抽象類或者Java中的接口,我們傳遞參數時可以傳遞具體實現類的對象(比如B或者C的對象),但在這里只是一個抽象

另外,我們的B和C都實現了consistent_method,這就是一致性的體現。

為什么

假設我們設計了某種功能,當用戶點擊頁面上的按鈕時,我們需要為用戶做兩件事,一是做頁面變換顯示出預期的效果,二是響起特定的音樂。那么我們的代碼可能是這樣的:

def onClick():
    changePage()
    playMusic()

后來你的需求有變動,還需要記錄用戶點擊時間,需要彈出提示信息,需要......
于是你需要不斷的修改上述的代碼,可能是這樣的

def onClick():
    changePage()
    playMusic()
    recordTime()
    popupHint()
    ...

我們認為onClick()和這些功能函數耦合的過于緊密了,每次的改動都會影響onClick函數。這類問題抽象出來就是當一個事件發生時需要調用很多功能模塊,而解決這類問題最好的方式就是觀察者模式,也叫發布-訂閱模式。

是什么

觀察者模式是說你有一個觀察者列表,這個列表中的函數或者某種功能都在觀察某個事件的發生,一旦發生,這些函數或者功能就會自動執行,這個其實很好理解,如下圖:

publisher_subscriber.jpg

我們還是上代碼:

class Button(object):
    """publisher or subject"""
    def __init__(self):
        self.observer_list = []

    def register(self, func):
        self.observer_list.append(func)

    def unregister(self, func):
        self.observer_list.remove(func)

    def onClick():
        for func in self.observer_list:
            func()
    ...

def playMusic():
     """subscriber or observer"""
    ...

def changePage():
    """subscriber or observer"""
    ...
...

def main():
    button = Button()
    button.register(playMusic)
    button.register(changePage)
    ...

通過這種方式,我們實現了發布者和訂閱者之間的松耦合,它們之間并不直接聯系,而是通過統一的register/unregister來綁定和解綁定。

怎么用

通過上面的代碼,細心的同學可能會觀察到似乎代碼也沒少啊,而且注冊的時候不還是要修改代碼去注冊新的功能嗎?能想到這的同學,你的批判性思維很好,鼓勵一下!
這里基于以下幾種好處我們要使用這個模式:

  • 使用這個模式的最大好處之一就是靈活
    我們可以動態的修改監聽的事件,比如用戶不想在點擊該按鈕的時候響起音樂,那么當ta不選擇這一項時,我們的程序可以靈活的通過unregister來解綁定,試想如果你不使用觀察者模式,是很難解綁定的,你就需要去修改代碼,顯然這不現實。
# when user uncheck play music option
button.unregister(playMusic)

所以,使用這個模式的理由之一就是你需要動態的綁定和解綁相應的功能時,你就需要觀察者模式。

  • 第二個好處是代碼復用
    比如你有很多個按鈕,都需要統一的注冊某些功能,這個時候你就可以實現一個父類,在初始化的時候將所有的需要注冊的功能都注冊好,子類直接繼承就好了,子類當然還可以注冊自己特殊功能。可能的代碼如下:
class Button(object):
    def __init__(self):
        self.observer_list = []
        self.observer_list.register(playMusic)
        self.observer_list.register(popupHint)
    
    def register(self, func):
        self.observer_list.append(func)
    
    def unregister(self, func):
        self.observer_list.remove(func)

    def onClick():
        for func in self.observer_list:
            func()


class ButtonA(Button):
    def __init__(self):
        super(ButtonA, self).__init__()
    ...

class ButtonB(Button):
    def __init__(self):
        super(ButtonA, self).__init__()
        self.observer_list.register(changePage)
    ...

好了,觀察者模式到這里你就應該很清楚了,如果你覺得有收獲,不妨點個贊,如果你覺得非常贊,那就打個賞,鼓勵是一種美德!??

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容