本系列文章是希望將軟件項(xiàng)目中最常見(jiàn)的設(shè)計(jì)模式用通俗易懂的語(yǔ)言來(lái)講解清楚振惰,并通過(guò)Python來(lái)實(shí)現(xiàn)歌溉,每個(gè)設(shè)計(jì)模式都是圍繞如下三個(gè)問(wèn)題:
- 為什么?即為什么要使用這個(gè)設(shè)計(jì)模式骑晶,在使用這個(gè)模式之前存在什么樣的問(wèn)題痛垛?
- 是什么?通過(guò)Python語(yǔ)言來(lái)去實(shí)現(xiàn)這個(gè)設(shè)計(jì)模式桶蛔,用于解決為什么中提到的問(wèn)題匙头。
- 怎么用?理解了為什么我們也就基本了解了什么情況下使用這個(gè)模式仔雷,不過(guò)在這里還是會(huì)細(xì)化使用場(chǎng)景蹂析,闡述模式的局限和優(yōu)缺點(diǎn)。
一些基本概念
在開(kāi)始講本次設(shè)計(jì)模式之前碟婆,我們先搞清楚軟件設(shè)計(jì)中的一些基本概念吧电抚。軟件設(shè)計(jì)中我們有一個(gè)常用的概念叫“耦合”,我們常常說(shuō)模塊之間要“解耦合”竖共,軟件設(shè)計(jì)要“松耦合”蝙叛。那么什么是“耦合”呢?
實(shí)際上耦合是指兩個(gè)模塊之間(模塊可以理解為兩坨代碼公给,所以可以是函數(shù)借帘,可以是類(lèi)蜘渣,或者更大范圍的系統(tǒng))發(fā)生了關(guān)系,比如一個(gè)模塊調(diào)用了另一個(gè)模塊的函數(shù)肺然,或者一個(gè)模塊需要獲取另一個(gè)模塊的數(shù)據(jù)蔫缸,總之有了聯(lián)系就有了耦合。所謂“解耦合”或者“松耦合”是說(shuō)讓兩個(gè)模塊之間的聯(lián)系沒(méi)有那么緊密狰挡,這樣一個(gè)模塊的變化不會(huì)影響另一個(gè)模塊的代碼捂龄。那么我們?nèi)绾蝸?lái)使我們的系統(tǒng)是一個(gè)“松耦合”的系統(tǒng)呢释涛?
實(shí)現(xiàn)“松耦合”最重要的就是抽象和一致性加叁,白話(huà)就是要通過(guò)抽象的接口(Python中沒(méi)有接口的概念,可以理解為抽象的類(lèi))來(lái)聯(lián)系調(diào)用方和被調(diào)方唇撬。
一般的它匕,假設(shè)我們實(shí)現(xiàn)了兩個(gè)類(lèi)A和B,我們可能會(huì)如下調(diào)用
代碼可能是這樣的
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ā)生改變豫柬,或者想換成另一個(gè)類(lèi)C,這時(shí)就需要更改A的代碼扑浸,那么如果我們把A和B之間的聯(lián)系抽象出來(lái)烧给,通過(guò)接口(或者類(lèi)似接口的東西)來(lái)連接A和B我們就可以某種程度上屏蔽這種變化,如下圖所示:
可能的代碼如下:
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對(duì)參數(shù)沒(méi)有類(lèi)型約束喝噪,所以天然的支持接口础嫡,這也是Python靈活的地方之一。在這個(gè)例子中酝惧,我們的參數(shù)some_obj可以想象成一個(gè)抽象類(lèi)或者Java中的接口榴鼎,我們傳遞參數(shù)時(shí)可以傳遞具體實(shí)現(xiàn)類(lèi)的對(duì)象(比如B或者C的對(duì)象),但在這里只是一個(gè)抽象晚唇。
另外巫财,我們的B和C都實(shí)現(xiàn)了consistent_method,這就是一致性的體現(xiàn)哩陕。
為什么
假設(shè)我們?cè)O(shè)計(jì)了某種功能平项,當(dāng)用戶(hù)點(diǎn)擊頁(yè)面上的按鈕時(shí),我們需要為用戶(hù)做兩件事悍及,一是做頁(yè)面變換顯示出預(yù)期的效果葵礼,二是響起特定的音樂(lè)。那么我們的代碼可能是這樣的:
def onClick():
changePage()
playMusic()
后來(lái)你的需求有變動(dòng)并鸵,還需要記錄用戶(hù)點(diǎn)擊時(shí)間鸳粉,需要彈出提示信息,需要......
于是你需要不斷的修改上述的代碼园担,可能是這樣的
def onClick():
changePage()
playMusic()
recordTime()
popupHint()
...
我們認(rèn)為onClick()和這些功能函數(shù)耦合的過(guò)于緊密了届谈,每次的改動(dòng)都會(huì)影響onClick函數(shù)枯夜。這類(lèi)問(wèn)題抽象出來(lái)就是當(dāng)一個(gè)事件發(fā)生時(shí)需要調(diào)用很多功能模塊,而解決這類(lèi)問(wèn)題最好的方式就是觀(guān)察者模式艰山,也叫發(fā)布-訂閱模式湖雹。
是什么
觀(guān)察者模式是說(shuō)你有一個(gè)觀(guān)察者列表,這個(gè)列表中的函數(shù)或者某種功能都在觀(guān)察某個(gè)事件的發(fā)生曙搬,一旦發(fā)生摔吏,這些函數(shù)或者功能就會(huì)自動(dòng)執(zhí)行,這個(gè)其實(shí)很好理解纵装,如下圖:
我們還是上代碼:
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)
...
通過(guò)這種方式征讲,我們實(shí)現(xiàn)了發(fā)布者和訂閱者之間的松耦合,它們之間并不直接聯(lián)系橡娄,而是通過(guò)統(tǒng)一的register/unregister來(lái)綁定和解綁定诗箍。
怎么用
通過(guò)上面的代碼,細(xì)心的同學(xué)可能會(huì)觀(guān)察到似乎代碼也沒(méi)少啊挽唉,而且注冊(cè)的時(shí)候不還是要修改代碼去注冊(cè)新的功能嗎滤祖?能想到這的同學(xué),你的批判性思維很好瓶籽,鼓勵(lì)一下匠童!
這里基于以下幾種好處我們要使用這個(gè)模式:
- 使用這個(gè)模式的最大好處之一就是靈活
我們可以動(dòng)態(tài)的修改監(jiān)聽(tīng)的事件,比如用戶(hù)不想在點(diǎn)擊該按鈕的時(shí)候響起音樂(lè)塑顺,那么當(dāng)ta不選擇這一項(xiàng)時(shí)汤求,我們的程序可以靈活的通過(guò)unregister來(lái)解綁定,試想如果你不使用觀(guān)察者模式茬暇,是很難解綁定的首昔,你就需要去修改代碼,顯然這不現(xiàn)實(shí)糙俗。
# when user uncheck play music option
button.unregister(playMusic)
所以勒奇,使用這個(gè)模式的理由之一就是你需要?jiǎng)討B(tài)的綁定和解綁相應(yīng)的功能時(shí),你就需要觀(guān)察者模式巧骚。
- 第二個(gè)好處是代碼復(fù)用
比如你有很多個(gè)按鈕赊颠,都需要統(tǒng)一的注冊(cè)某些功能,這個(gè)時(shí)候你就可以實(shí)現(xiàn)一個(gè)父類(lèi)劈彪,在初始化的時(shí)候?qū)⑺械男枰?cè)的功能都注冊(cè)好竣蹦,子類(lèi)直接繼承就好了,子類(lèi)當(dāng)然還可以注冊(cè)自己特殊功能沧奴《焕ǎ可能的代碼如下:
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)
...
好了,觀(guān)察者模式到這里你就應(yīng)該很清楚了,如果你覺(jué)得有收獲纲菌,不妨點(diǎn)個(gè)贊挠日,如果你覺(jué)得非常贊,那就打個(gè)賞翰舌,鼓勵(lì)是一種美德嚣潜!??