忘了又看,看了又忘?保姆級(jí)教學(xué),一口氣教你玩轉(zhuǎn)三種高頻設(shè)計(jì)模式!
大家好,我是樓仔呀。
無(wú)論大家工作還是面試,都會(huì)用到設(shè)計(jì)模式,如果不結(jié)合具體的場(chǎng)景,通過(guò)書(shū)本學(xué)到的設(shè)計(jì)模式非常容易忘。
本文通過(guò)具體的示例,教大家如何學(xué)習(xí)設(shè)計(jì)模式,保證你看完這篇文章后,這 3 種常用的設(shè)計(jì)模式,能妥妥掌握!
不 BB,上文章目錄。
1. 一起打豆豆
有個(gè)記者去南極采訪(fǎng)一群企鵝,他問(wèn)第一只企鵝:“你每天都干什么?”
企鵝說(shuō):“吃飯,睡覺(jué),打豆豆!”
接著又問(wèn)第 2 只企鵝,那只企鵝還是說(shuō):“吃飯,睡覺(jué),打豆豆!”
記者帶著困惑問(wèn)其他的企鵝,答案都一樣,就這樣一直問(wèn)了 99 只企鵝。
當(dāng)走到第 100 只小企鵝旁邊時(shí),記者走過(guò)去問(wèn)它:每天都做些什么啊?
那只小企鵝回答:"吃飯,睡覺(jué)."
記者驚奇的又問(wèn):"你怎么不打豆豆?"
小企鵝撇著嘴巴,瞪了記者一眼說(shuō):"我就是豆豆!"
樓哥,你搞錯(cuò)了吧,這是篇技術(shù)文,你咋講笑話(huà)了?甭著急,繼續(xù)往后面看哈~~
2. 模板&策略模式
2.1 實(shí)現(xiàn)姿勢(shì)
我們會(huì)從簡(jiǎn)單到復(fù)雜,講解代碼正確的實(shí)現(xiàn)姿勢(shì),分別為最 Low 方式、常規(guī)方式、模板模式和策略模式。
2.1.1 最 Low 方式
假如現(xiàn)在有 3 只企鵝,都喜歡 “吃飯,睡覺(jué),打豆豆”:
看一下執(zhí)行結(jié)果:
這種方式是大家寫(xiě)代碼時(shí),最容易使用的方式,上手簡(jiǎn)單,也容易理解,目前看項(xiàng)目中陳舊的代碼,經(jīng)常能找到它們的影子,下面我們看怎么一步步將其進(jìn)行重構(gòu)。
2.1.2 常規(guī)方式
“吃飯,睡覺(jué),打豆豆” 其實(shí)都是獨(dú)立的行為,為了不相互影響,我們可以通過(guò)函數(shù)簡(jiǎn)單進(jìn)行封裝:
工作過(guò)一段時(shí)間的同學(xué),可能會(huì)采用這種實(shí)現(xiàn)方式,我們有沒(méi)有更優(yōu)雅的實(shí)現(xiàn)方式呢?
2.1.3 模板模式
定義:一個(gè)抽象類(lèi)公開(kāi)定義了執(zhí)行它的方法的方式/模板,它的子類(lèi)可以按需要重寫(xiě)方法實(shí)現(xiàn),但調(diào)用將以抽象類(lèi)中定義的方式進(jìn)行,屬于行為型模式。
這 3 只企鵝,因?yàn)?“吃飯,睡覺(jué)” 都一樣,所以我們可以直接實(shí)現(xiàn)出來(lái),但是他們 “打豆豆” 的方式不同,所以封裝成抽象方法,需要每個(gè)企鵝單獨(dú)去實(shí)現(xiàn) “打豆豆” 的方式。
最后再新增一個(gè)方法 everyDay(),固定每天的執(zhí)行流程:
每只企鵝單獨(dú)實(shí)現(xiàn)自己 “打豆豆” 的方式:
最后看調(diào)用方式:
樓哥,你這代碼看的費(fèi)勁,能給我畫(huà)一個(gè) UML 圖么?來(lái),安排!
2.1.4 策略模式
定義:一個(gè)類(lèi)的行為或其算法可以在運(yùn)行時(shí)更改,即我們創(chuàng)建表示各種策略的對(duì)象和一個(gè)行為隨著策略對(duì)象改變而改變的 context 對(duì)象,策略對(duì)象改變 context 對(duì)象的執(zhí)行算法,屬于行為型模式。
我們還是先抽象出 3 個(gè)企鵝的行為:
每只企鵝單獨(dú)實(shí)現(xiàn)自己 “打豆豆” 的方式:
這里就是策略模式的重點(diǎn),我們?cè)倏匆幌虏呗阅J降亩x “我們創(chuàng)建表示各種策略的對(duì)象和一個(gè)行為隨著策略對(duì)象改變而改變的 context 對(duì)象”,那么該 contex 對(duì)象如下:
最后看調(diào)用方式:
我們可以通過(guò)給 behaviorContext 傳遞不同的對(duì)象,然后來(lái)約定 everyDay() 的調(diào)用方式。
其實(shí)我這個(gè)示例,有點(diǎn)把策略模式講復(fù)雜了,因?yàn)榧兇獾牟呗阅J剑? 個(gè)企鵝只有 beating() 方法不同,所以可以把 beating() 理解為不同的算法即可。
之所以引入 everyDay(),是因?yàn)閷?shí)際的項(xiàng)目場(chǎng)景中,會(huì)經(jīng)常這么使用,也就是把這個(gè)變化的算法 beating(),包裝到具體的執(zhí)行流程里面,所以策略模式就看起來(lái)沒(méi)有那么直觀,但是核心思想是一樣的。
2.2 模板 vs 策略
我在選擇模板模式和策略模式時(shí),發(fā)現(xiàn)兩者都可以完全滿(mǎn)足我的需求,然后我到網(wǎng)上查閱了很多資料,希望能找到兩種模式在技術(shù)選擇時(shí),能確定告訴我哪些情況需要選擇哪種模式。
說(shuō)來(lái)慚愧,到現(xiàn)在我都沒(méi)有找到,因?yàn)榫W(wǎng)上只告訴我兩種實(shí)現(xiàn)姿勢(shì)的區(qū)別,但是沒(méi)有說(shuō)明如何具體選型。
2.2.1 網(wǎng)上觀點(diǎn)
據(jù)我可以告訴他們是 99% 相同,唯一的區(qū)別是模板方法模式具有抽象類(lèi)作為基類(lèi),而戰(zhàn)略類(lèi)使用由每個(gè)具體戰(zhàn)略類(lèi)實(shí)現(xiàn)的接口,兩者的主要區(qū)別在于具體 algorithm 的 select。
使用 Template 方法模式時(shí),通過(guò)子類(lèi)化模板在編譯時(shí)發(fā)生,每個(gè)子類(lèi)通過(guò)實(shí)現(xiàn)模板的抽象方法提供了一個(gè)不同的具體 algorithm。
當(dāng)客戶(hù)端調(diào)用模板的外部接口的方法時(shí),模板根據(jù)需要調(diào)用其抽象方法(其內(nèi)部接口)來(lái)調(diào)用 algorithm。
相比之下,策略模式允許在運(yùn)行時(shí)通過(guò)遏制來(lái) select algorithm,具體 algorithm 是通過(guò)單獨(dú)的類(lèi)或函數(shù)實(shí)現(xiàn)的,這些類(lèi)或函數(shù)作為 parameter passing 給構(gòu)造函數(shù)或構(gòu)造方法。
上面講的有點(diǎn)抽象,下面直接看具體對(duì)比。
相似:
- 策略和模板方法模式都可以用來(lái)滿(mǎn)足開(kāi)閉原則,使得軟件模塊在不改變代碼的情況下易于擴(kuò)展;
- 兩種模式都表示通用 function 與該 function 的詳細(xì)實(shí)現(xiàn)的分離,不過(guò)它們所提供的粒度有一些差異。
差異:
- 策略模式:
它基于接口;
客戶(hù)和策略之間的耦合更加松散;
定義不能被子類(lèi)改變的 algorithm 的骨架,只有某些操作可以在子類(lèi)中重寫(xiě);
父類(lèi)完全控制 algorithm ,僅將具體的步驟與具體的類(lèi)進(jìn)行區(qū)分;
綁定是在編譯時(shí)完成的。
- 模板模式:
它基于框架或抽象類(lèi),甚至可以有一個(gè)具有默認(rèn)實(shí)現(xiàn)的具體類(lèi)。
模塊耦合得更緊密;
它通過(guò)修改方法的行為來(lái)改變對(duì)象的內(nèi)容;
它用于在 algorithm 族之間切換;
它在運(yùn)行時(shí)通過(guò)其他 algorithm 完全 replace 一個(gè)algorithm 來(lái)改變對(duì)象的行為;
綁定在運(yùn)行時(shí)完成。
2.2.2 個(gè)人理解
對(duì)于有強(qiáng)迫癥的我,沒(méi)有找到問(wèn)題的根源,總感覺(jué)哪里不對(duì)勁,我就說(shuō)一下我對(duì)于兩者區(qū)別的理解吧。
說(shuō)實(shí)話(huà),兩種設(shè)計(jì)模式,我也就看到在實(shí)現(xiàn)姿勢(shì)上有所區(qū)別,至于說(shuō)的策略模式要定義統(tǒng)一接口,模板模式不這樣做等,我不太贊同,因?yàn)槲矣袝r(shí)也會(huì)給模板模式定義一個(gè)通用接口。
然后也有人說(shuō),策略模式需要定義一堆對(duì)象,模板模式就不需要,如果有 10 個(gè)不同的企鵝,模板模式不也是需要定義 10 個(gè)不同的企鵝類(lèi),然后再專(zhuān)門(mén)針對(duì)特定的方法去實(shí)現(xiàn)么?
這兩種設(shè)計(jì)模式,我感覺(jué)還沒(méi)有到非此即彼的劃分。
如果我沒(méi)有固定的執(zhí)行流程,比如只去打豆豆,只需要對(duì)一個(gè)方法做具體抽象,我愿意選擇策略模式,因?yàn)檫@個(gè)我感覺(jué)會(huì)讓我需要使用的對(duì)象,更清晰一些。
如果我有固定的執(zhí)行流程,比如 “吃飯、睡覺(jué)、打豆豆”,我更愿意使用模板方法,可能是代碼看多了,也看習(xí)慣了,更愿意用模板方法去規(guī)范代碼固定的執(zhí)行流程。
當(dāng)然,我也可以將兩者結(jié)合起來(lái)使用,比如我們可以用模板方法,去實(shí)現(xiàn)這 3 只企鵝,但是對(duì)于 middlePenguin,可能有分為企鵝少年 A、企鵝少年 B、企鵝少年 C,他們都喜歡隔壁的企鵝妹妹,但是喜歡的方式不同,有暗戀的,有直接表白的,還有霸道總裁的,我可以用策略模式,去指定他們對(duì)企鵝妹妹的表達(dá)方式。
2.3 實(shí)際場(chǎng)景
任何模式,都需要結(jié)合實(shí)際的場(chǎng)景來(lái)講,才能更清晰。
這兩個(gè)模式,可以在你之前做過(guò)的項(xiàng)目中,只要稍微留意一下,應(yīng)該會(huì)發(fā)現(xiàn)它們其實(shí)是大量存在的。
比如很多框架代碼,里面有很多固定的執(zhí)行流程,有些邏輯是可以采用默認(rèn)處理的方式,有些邏輯需要下游自己去實(shí)現(xiàn),然后有些邏輯還需要提前預(yù)留鉤子。
比如在執(zhí)行 process() 流程時(shí),可能需要進(jìn)行 preProcess() 的操作,那么這個(gè) preProcess() 就是你預(yù)留的鉤子,下游可以實(shí)現(xiàn),也可以不實(shí)現(xiàn)。
3. 工廠(chǎng)模式
后面會(huì)結(jié)合模板模式,來(lái)講解工廠(chǎng)模式,實(shí)戰(zhàn)場(chǎng)景非常強(qiáng)。
3.1 問(wèn)題引入
對(duì)于工廠(chǎng)模式,大家可能覺(jué)得會(huì)很 Low,不就是搞個(gè)類(lèi),然后專(zhuān)門(mén)生成一個(gè)具體的對(duì)象嘛,這有什么難的?
是的,工廠(chǎng)模式確實(shí)不難,但是問(wèn)你一下,如果你的代碼中有很多 if...else,你知道怎么通過(guò)工廠(chǎng)模式,把這些 if...else 去掉么?
嗯,工廠(chǎng)模式我會(huì),但是和去掉 if...else 好像沒(méi)有關(guān)系吧?
我舉個(gè)例子,假如你遇到如下代碼:
既然你懂工廠(chǎng)模式,可以把 if...else 簡(jiǎn)單重構(gòu)一下,那就開(kāi)始你的表演吧。
什么?不會(huì)?!你剛才還是自己是會(huì)工廠(chǎng)模式,怎么突然就慫了呢?
既然不會(huì),那就靜下心來(lái),虛心學(xué)習(xí)一下。
3.2 工廠(chǎng)模式
定義:它提供了一種創(chuàng)建對(duì)象的最佳方式,我們?cè)趧?chuàng)建對(duì)象時(shí)不會(huì)對(duì)客戶(hù)端暴露創(chuàng)建邏輯,并且是通過(guò)使用一個(gè)共同的接口來(lái)指向新創(chuàng)建的對(duì)象,屬于創(chuàng)建型模式。
先直接上圖,后面的示例主要通過(guò)該圖展開(kāi):
其實(shí)設(shè)計(jì)模式一般不會(huì)單一使用,通常會(huì)和其它模式結(jié)合起來(lái)使用,這里我們就將上一篇文章講到的模板模式和工廠(chǎng)模式結(jié)合起來(lái)。
因?yàn)楣S(chǎng)模式,通常會(huì)給這些新創(chuàng)建的對(duì)象制定一個(gè)公共的接口,我們可以通過(guò)抽象類(lèi)定義:
因?yàn)槲覀兪墙Y(jié)合了模板模式,所以這個(gè)抽象類(lèi)中,可以看到模板模式的影子。
如果你只關(guān)注抽象的接口,比如 beating,那么這個(gè)就是一個(gè)抽象方法,也可以理解為下游需要實(shí)現(xiàn)的方法,其它的接口其實(shí)可以忽略。
看看每個(gè)企鵝具體的實(shí)現(xiàn):
這里是工廠(chǎng)方法的重點(diǎn),需要構(gòu)建一個(gè)工廠(chǎng),專(zhuān)門(mén)用來(lái)拿企鵝:
上面的邏輯很簡(jiǎn)單,就是通過(guò)一個(gè) map 對(duì)象,放入所有的企鵝,這個(gè)工廠(chǎng)就可以通過(guò)企鵝的名字,拿到對(duì)應(yīng)的企鵝對(duì)象,最后我們看使用方式:
輸出如下:
樓哥,你這個(gè)例子我看懂了,但是你最開(kāi)始拋的那個(gè)問(wèn)題,能給出答案么?
3.3 問(wèn)題解答
文章開(kāi)頭的這個(gè)示例,其實(shí)也是我最近需要重構(gòu)項(xiàng)目中的一段代碼,我就是用 “工廠(chǎng)模式 + 模板模式” 來(lái)重構(gòu)的。
我首先會(huì)對(duì)每個(gè)方法中的內(nèi)容通過(guò)模板模式進(jìn)行抽象(因?yàn)楸菊轮饕v工廠(chǎng)模式,模板模式的代碼,我就不貼了),然后通過(guò)工廠(chǎng)模式獲取不同的對(duì)象,直接看重構(gòu)后的代碼:
3.4 實(shí)際場(chǎng)景
這個(gè)場(chǎng)景就太多了,剛才給大家講解的是去掉 if...else 的場(chǎng)景,然后在小米商城的支付系統(tǒng)中,因?yàn)楹M庥袔资N支付方式,也是通過(guò)這種方式去掉 if...else 的,不過(guò)支付類(lèi)的封裝不是用的模板方法,用的的策略模式,雖然感覺(jué)兩者差不多。
如果你直接 new 一個(gè)對(duì)象就能解決的問(wèn)題,就用不到工廠(chǎng)模式了。
4. 結(jié)語(yǔ)
看完這篇文章,相信這 3 種設(shè)計(jì)模式,已經(jīng)深深刻在你骨子里面了。
大家可以靜下心來(lái)想想,自己之前做過(guò)的項(xiàng)目中,有哪些用到上面這 3 種設(shè)計(jì)模式,然后自己再結(jié)合具體的場(chǎng)景總結(jié)一下,我想你應(yīng)該會(huì)有更深入的理解。