如何真正理解好一個(gè)「設(shè)計(jì)模式」?

我的施工之路
4列表專題
真正理解設(shè)計(jì)模式
設(shè)計(jì)模式是無(wú)數(shù)開(kāi)發(fā)者前輩,經(jīng)過(guò)大量編碼實(shí)踐,總結(jié)下來(lái)的一套能提高程序擴(kuò)展性、可復(fù)用性的哲學(xué)。它就像建筑大師多年經(jīng)驗(yàn)沉淀下來(lái)的樓宇設(shè)計(jì)方法,又像武俠小說(shuō)中的武林高手擊敗對(duì)手的武林秘籍。
1 設(shè)計(jì)模式的由來(lái)
實(shí)話講,很多開(kāi)發(fā)者初次接觸設(shè)計(jì)模式時(shí),覺(jué)得它太玄學(xué),明明封裝為一個(gè)對(duì)象就能解決問(wèn)題,為啥非要?jiǎng)?chuàng)建多個(gè)對(duì)象,各個(gè)對(duì)象還有引用關(guān)系,既不簡(jiǎn)約,也不可讀。學(xué)完幾個(gè)設(shè)計(jì)模式,最后真心覺(jué)得設(shè)計(jì)模式?jīng)]用!
直到接手一個(gè)大項(xiàng)目時(shí),對(duì)設(shè)計(jì)模式的認(rèn)識(shí)才漸漸有所改變??蛻舻男枨罂倳?huì)變,幾天一個(gè)樣。于是,開(kāi)發(fā)者總要去改動(dòng)原來(lái)的類或方法。好不容易上線,客戶需求還在變,于是開(kāi)發(fā)者再回去修改原來(lái)的方法??蛻粜滦枨蟠_實(shí)實(shí)現(xiàn)了,但不要求改動(dòng)的某些功能卻意外出現(xiàn)bug,這令開(kāi)發(fā)者非常撓頭。
于是,這些前輩們,痛定思定,要設(shè)計(jì)出一套開(kāi)發(fā)模式,既能保證住原功能的穩(wěn)定性,同時(shí)也能實(shí)現(xiàn)客戶需求變化。
這才有了設(shè)計(jì)模式。
2 面向特定場(chǎng)景
前輩們發(fā)現(xiàn),為了同時(shí)實(shí)現(xiàn)原功能和新功能,一種設(shè)計(jì)模式很難做到。不同的需求場(chǎng)景,對(duì)應(yīng)開(kāi)發(fā)出不同的設(shè)計(jì)模式,久而久之,沉淀下十幾種經(jīng)典常用的設(shè)計(jì)模式。
這些設(shè)計(jì)模式大概可分類為:創(chuàng)建對(duì)象的設(shè)計(jì)方法,定義行為的設(shè)計(jì)方法。至于創(chuàng)建對(duì)象的設(shè)計(jì)模式,前輩們根據(jù)具體的場(chǎng)景不同,又制定出幾種方法;定義行為的方法,也根據(jù)場(chǎng)景不同定義出不同的設(shè)計(jì)方法。
3 對(duì)象工廠
這是一種創(chuàng)建對(duì)象的設(shè)計(jì)模式。誕生它的初衷之一,是因?yàn)樵O(shè)計(jì)出了多個(gè)子類,導(dǎo)致這些類的使用者調(diào)用起來(lái)不是很便捷,于是他們對(duì)開(kāi)發(fā)這些類的作者提出需求,需要增加一個(gè)對(duì)象工廠類來(lái)管理子類,由對(duì)象工廠組裝出不同的子類對(duì)象。
這樣,使用者只需找到對(duì)象工廠類,調(diào)用它創(chuàng)建出工廠里的任意一個(gè)對(duì)象。
大家注意:設(shè)計(jì)模式與具體的實(shí)現(xiàn)語(yǔ)言無(wú)關(guān),它是一種提高面向?qū)ο罂蓮?fù)用性、可擴(kuò)展性的設(shè)計(jì)思想。一般來(lái)講,設(shè)計(jì)模式普遍使用的語(yǔ)言包括:Java、C#、Python等。
此處是講設(shè)計(jì)模式,簡(jiǎn)化語(yǔ)言實(shí)現(xiàn),重點(diǎn)幫助大家理解設(shè)計(jì)模式,因此不要糾結(jié)語(yǔ)法,你可以理解為下面是偽代碼
首先定義一個(gè)接口:
class?Interface(object):
??def?createCar():
????pass
如下定義 3 個(gè)實(shí)現(xiàn)接口的類:
class?A(Interface):
??def?createCar():
????print('A-method')
class?B(Interface):
??def?createCar():
????print('B-method')
??
class?C(Interface):
??def?createCar():
????print('C-method')
創(chuàng)建一個(gè)對(duì)象工廠,專門(mén)用于創(chuàng)建A或B或C:
class?CarFactory(object):
??def?getObject(methodStr):
????if?methodStr?==?'A':
??????return?A()?#?返回A對(duì)象
????if?methodStr?==?'B':
??????return?B()
????if?methodStr?==?'C':
??????return?C()
使用時(shí),通過(guò) CarFactory().getObject('C') 得到C對(duì)象,調(diào)用C對(duì)象的方法createCar就能根據(jù)此方法造車。
4 思考一下
學(xué)習(xí)設(shè)計(jì)模式的最終目標(biāo)是要用到實(shí)際開(kāi)發(fā)中,要靈活運(yùn)用,要養(yǎng)成一種使用直覺(jué)。上版對(duì)象工廠實(shí)現(xiàn),大家對(duì)其有何預(yù)期?
首先來(lái)看,如果將來(lái)生成Car又增加一種D方法,于是乎,需要增加下面的代碼:
新增一個(gè)類D,這是沒(méi)有問(wèn)題的,符合面向?qū)ο蟮目蓴U(kuò)展性:
class?D(Interface):
??def?createCar():
????print('D-method')
但是對(duì)象工廠CarFactory這個(gè)模塊就要修改內(nèi)部的方法getObject,增加一條生成D對(duì)象的分支。但這確實(shí)破壞了類的封裝!
為解決此問(wèn)題,實(shí)際上還可以進(jìn)一步抽象,進(jìn)一步擴(kuò)展出幾個(gè)類。比如增加一個(gè)抽象工廠類:
class?CarFactoryInterface(object):
??pass
重新創(chuàng)建一個(gè)實(shí)現(xiàn)接口的工廠類:CarFactoryExtend,從而不用修改用來(lái)的類文件。
class?CarFactoryExtend(CarFactoryInterface):
????def?getObject(methodStr):
????if?methodStr?==?'A':
??????return?A()?#?返回A對(duì)象
????if?methodStr?==?'B':
??????return?B()
????if?methodStr?==?'C':
??????return?C()
????if?methodStr?==?'D':
???????return?D()
以上設(shè)計(jì)模式就是所謂的抽象工廠模式。你看,這些設(shè)計(jì)模式的形成都是由需求背景的。因此,不是先有設(shè)計(jì)模式后,開(kāi)發(fā)者們循著設(shè)計(jì)模式去解決實(shí)際需求;而恰恰相反,是有了源源不斷的開(kāi)發(fā)需求后,日積月累沉淀下這十幾種實(shí)際模式。并被后來(lái)的開(kāi)發(fā)者們爭(zhēng)相模仿學(xué)習(xí),更是被領(lǐng)悟其思想精髓者,大呼其好用。
5 設(shè)計(jì)禁忌
設(shè)計(jì)模式的幾個(gè)禁忌,大概總結(jié)為以下幾點(diǎn):
不是越抽象越好,也不是不抽象,而是要把握好一個(gè)度; 繼承鏈條的根不要是具體的實(shí)現(xiàn)類,因?yàn)榫唧w不等于抽象,根最好是接口或抽象類; 不要生搬硬套各種設(shè)計(jì)模式,雖然每個(gè)模式都有一個(gè)標(biāo)準(zhǔn)版本,但日常使用一般不是死板的模仿,一個(gè)角色都不能少; 設(shè)計(jì)模式不是無(wú)用的,如果喜歡總結(jié),再工作幾年后,腦子里會(huì)有幾個(gè)常用設(shè)計(jì)模式; 沒(méi)有一個(gè)通用的設(shè)計(jì)模式,一個(gè)設(shè)計(jì)模式往往只針對(duì)某個(gè)特定場(chǎng)景。
6 練習(xí)一個(gè)設(shè)計(jì)模式
有一種設(shè)計(jì)模式常被用于算法開(kāi)發(fā),先不說(shuō)它的名字,我們根據(jù)實(shí)際的需求場(chǎng)景,倒推出這個(gè)設(shè)計(jì)模式。
解決某個(gè)特定問(wèn)題可以使用策略A類里的方法solve:
class?A(object):
??def?solver():
????print('A?method')
后來(lái)又發(fā)明方法B類:
class?B(object):
??def?solver():
????print('B?method')
設(shè)計(jì)模式最重要一條:繼承鏈條的根要是抽象類或接口,因此提取出接口StrategyInterface:
class?StrategyInterface(object):
????def?solver():?#?這是接口的方法
??????pass
所以,A類和B類稍作修改:
class?A(StrategyInterface):
??def?solver():
????print('A?method')
class?B(StrategyInterface):
??def?solver():
????print('B?method')
使用方在使用這些策略時(shí),到底該使用哪個(gè)策略呢?為了方便策略管理,又多出一個(gè)策略管理類:
class?StrategyContext(object):
??def?setStrategy(StrategyInterface):
????self.strategy?=?StrategyInterface
??def?callMethod():
????print('context?of?strategy')
????self.strategy.solver()
????print('done')#?other?things????
實(shí)際使用時(shí)的方法:
context?=?StrategyContext()
context.setStrategy(A())?
context.callMethod()
以上就是策略模式,是一種關(guān)于行為控制的設(shè)計(jì)模式。
如果不想要StrategyContext類,實(shí)際使用時(shí)的方法如下:
StrategyInterface?strategy?=?A()
print('before?strategy')
strategy.solver()
print('done')#?other?things????
這樣又未嘗不可呢,繼承鏈?zhǔn)諗坑诮涌?,只不過(guò)使用者需要多寫(xiě)一些可能不是"太標(biāo)準(zhǔn)"的代碼。依賴于設(shè)計(jì)模式,又能獨(dú)立思考某些設(shè)計(jì)模式,這樣更有可能靈活使用設(shè)計(jì)模式。
Python與算法社區(qū)
一個(gè)寫(xiě)了400+篇原創(chuàng)的技術(shù)號(hào)
