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

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

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

一些基本概念

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

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發(fā)生改變,或者想換成另一個類C,這時就需要更改A的代碼,那么如果我們把A和B之間的聯(lián)系抽象出來,通過接口(或者類似接口的東西)來連接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對參數(shù)沒有類型約束,所以天然的支持接口,這也是Python靈活的地方之一。在這個例子中,我們的參數(shù)some_obj可以想象成一個抽象類或者Java中的接口,我們傳遞參數(shù)時可以傳遞具體實現(xiàn)類的對象(比如B或者C的對象),但在這里只是一個抽象。

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

為什么

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

def onClick():
    changePage()
    playMusic()

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

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

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

是什么

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

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)
    ...

通過這種方式,我們實現(xiàn)了發(fā)布者和訂閱者之間的松耦合,它們之間并不直接聯(lián)系,而是通過統(tǒng)一的register/unregister來綁定和解綁定。

怎么用

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

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

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

  • 第二個好處是代碼復(fù)用
    比如你有很多個按鈕,都需要統(tǒng)一的注冊某些功能,這個時候你就可以實現(xiàn)一個父類,在初始化的時候?qū)⑺械男枰缘墓δ芏甲院?,子類直接繼承就好了,子類當(dāng)然還可以注冊自己特殊功能??赡艿拇a如下:
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)
    ...

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容