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

我的施工之路
4列表專題
真正理解設(shè)計(jì)模式
設(shè)計(jì)模式是無數(shù)開發(fā)者前輩,經(jīng)過大量編碼實(shí)踐,總結(jié)下來的一套能提高程序擴(kuò)展性、可復(fù)用性的哲學(xué)。它就像建筑大師多年經(jīng)驗(yàn)沉淀下來的樓宇設(shè)計(jì)方法,又像武俠小說中的武林高手擊敗對(duì)手的武林秘籍。
1 設(shè)計(jì)模式的由來
實(shí)話講,很多開發(fā)者初次接觸設(shè)計(jì)模式時(shí),覺得它太玄學(xué),明明封裝為一個(gè)對(duì)象就能解決問題,為啥非要?jiǎng)?chuàng)建多個(gè)對(duì)象,各個(gè)對(duì)象還有引用關(guān)系,既不簡(jiǎn)約,也不可讀。學(xué)完幾個(gè)設(shè)計(jì)模式,最后真心覺得設(shè)計(jì)模式?jīng)]用!
直到接手一個(gè)大項(xiàng)目時(shí),對(duì)設(shè)計(jì)模式的認(rèn)識(shí)才漸漸有所改變。客戶的需求總會(huì)變,幾天一個(gè)樣。于是,開發(fā)者總要去改動(dòng)原來的類或方法。好不容易上線,客戶需求還在變,于是開發(fā)者再回去修改原來的方法。客戶新需求確實(shí)實(shí)現(xiàn)了,但不要求改動(dòng)的某些功能卻意外出現(xiàn)bug,這令開發(fā)者非常撓頭。
于是,這些前輩們,痛定思定,要設(shè)計(jì)出一套開發(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)開發(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)用起來不是很便捷,于是他們對(duì)開發(fā)這些類的作者提出需求,需要增加一個(gè)對(duì)象工廠類來管理子類,由對(duì)象工廠組裝出不同的子類對(duì)象。
這樣,使用者只需找到對(duì)象工廠類,調(diào)用它創(chuàng)建出工廠里的任意一個(gè)對(duì)象。
大家注意:設(shè)計(jì)模式與具體的實(shí)現(xiàn)語言無關(guān),它是一種提高面向?qū)ο罂蓮?fù)用性、可擴(kuò)展性的設(shè)計(jì)思想。一般來講,設(shè)計(jì)模式普遍使用的語言包括:Java、C#、Python等。
此處是講設(shè)計(jì)模式,簡(jiǎn)化語言實(shí)現(xiàn),重點(diǎn)幫助大家理解設(shè)計(jì)模式,因此不要糾結(jié)語法,你可以理解為下面是偽代碼
首先定義一個(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ì)象工廠,專門用于創(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í),通過 CarFactory().getObject('C') 得到C對(duì)象,調(diào)用C對(duì)象的方法createCar就能根據(jù)此方法造車。
4 思考一下
學(xué)習(xí)設(shè)計(jì)模式的最終目標(biāo)是要用到實(shí)際開發(fā)中,要靈活運(yùn)用,要養(yǎng)成一種使用直覺。上版對(duì)象工廠實(shí)現(xiàn),大家對(duì)其有何預(yù)期?
首先來看,如果將來生成Car又增加一種D方法,于是乎,需要增加下面的代碼:
新增一個(gè)類D,這是沒有問題的,符合面向?qū)ο蟮目蓴U(kuò)展性:
class?D(Interface):
??def?createCar():
????print('D-method')
但是對(duì)象工廠CarFactory這個(gè)模塊就要修改內(nèi)部的方法getObject,增加一條生成D對(duì)象的分支。但這確實(shí)破壞了類的封裝!
為解決此問題,實(shí)際上還可以進(jìn)一步抽象,進(jìn)一步擴(kuò)展出幾個(gè)類。比如增加一個(gè)抽象工廠類:
class?CarFactoryInterface(object):
??pass
重新創(chuàng)建一個(gè)實(shí)現(xiàn)接口的工廠類:CarFactoryExtend,從而不用修改用來的類文件。
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ì)模式后,開發(fā)者們循著設(shè)計(jì)模式去解決實(shí)際需求;而恰恰相反,是有了源源不斷的開發(fā)需求后,日積月累沉淀下這十幾種實(shí)際模式。并被后來的開發(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ì)模式不是無用的,如果喜歡總結(jié),再工作幾年后,腦子里會(huì)有幾個(gè)常用設(shè)計(jì)模式; 沒有一個(gè)通用的設(shè)計(jì)模式,一個(gè)設(shè)計(jì)模式往往只針對(duì)某個(gè)特定場(chǎng)景。
6 練習(xí)一個(gè)設(shè)計(jì)模式
有一種設(shè)計(jì)模式常被用于算法開發(fā),先不說它的名字,我們根據(jù)實(shí)際的需求場(chǎng)景,倒推出這個(gè)設(shè)計(jì)模式。
解決某個(gè)特定問題可以使用策略A類里的方法solve:
class?A(object):
??def?solver():
????print('A?method')
后來又發(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)諗坑诮涌冢徊贿^使用者需要多寫一些可能不是"太標(biāo)準(zhǔn)"的代碼。依賴于設(shè)計(jì)模式,又能獨(dú)立思考某些設(shè)計(jì)模式,這樣更有可能靈活使用設(shè)計(jì)模式。
Python與算法社區(qū)
一個(gè)寫了400+篇原創(chuàng)的技術(shù)號(hào)
