學(xué)習(xí)《設(shè)計(jì)模式之美》筆記呆盖。
23 種經(jīng)典設(shè)計(jì)模式共分為 3 種類型拖云,分別是創(chuàng)建型、結(jié)構(gòu)型和行為型
創(chuàng)建型模式
創(chuàng)建型設(shè)計(jì)模式包括:?jiǎn)卫J接τ帧⒐S模式宙项、建造者模式、原型模式株扛。它主要解決對(duì)象的創(chuàng)建問(wèn)題尤筐,封裝復(fù)雜的創(chuàng)建過(guò)程,解耦對(duì)象的創(chuàng)建代碼和使用代碼
1. 單例模式
單例模式用來(lái)創(chuàng)建全局唯一的對(duì)象洞就。一個(gè)類只允許創(chuàng)建一個(gè)對(duì)象(或者叫實(shí)例)盆繁,那這個(gè)類就是一個(gè)單例類,這種設(shè)計(jì)模式就叫作單例模式旬蟋。單例有幾種經(jīng)典的實(shí)現(xiàn)方式油昂,它們分別是:餓漢式、懶漢式倾贰、雙重檢測(cè)冕碟、靜態(tài)內(nèi)部類、枚舉匆浙。
盡管單例是一個(gè)很常用的設(shè)計(jì)模式安寺,在實(shí)際的開發(fā)中,我們也確實(shí)經(jīng)常用到它首尼,但是挑庶,有些人認(rèn)為單例是一種反模式(anti-pattern),并不推薦使用软能,主要的理由有以下幾點(diǎn):
- 單例對(duì) OOP 特性的支持不友好
- 單例會(huì)隱藏類之間的依賴關(guān)系
- 單例對(duì)代碼的擴(kuò)展性不友好
- 單例對(duì)代碼的可測(cè)試性不友好
- 單例不支持有參數(shù)的構(gòu)造函數(shù)
那有什么替代單例的解決方案呢挠羔?如果要完全解決這些問(wèn)題,我們可能要從根上尋找其他方式來(lái)實(shí)現(xiàn)全局唯一類埋嵌。比如破加,通過(guò)工廠模式、IOC 容器來(lái)保證全局唯一性雹嗦。
人把單例當(dāng)作反模式范舀,主張杜絕在項(xiàng)目中使用合是。我個(gè)人覺得這有點(diǎn)極端。模式本身沒(méi)有對(duì)錯(cuò)锭环,關(guān)鍵看你怎么用聪全。如果單例類并沒(méi)有后續(xù)擴(kuò)展的需求,并且不依賴外部系統(tǒng)辅辩,那設(shè)計(jì)成單例類就沒(méi)有太大問(wèn)題难礼。對(duì)于一些全局類,我們?cè)谄渌胤?new 的話玫锋,還要在類之間傳來(lái)傳去蛾茉,不如直接做成單例類,使用起來(lái)簡(jiǎn)潔方便撩鹿。
除此之外谦炬,我們還講到了進(jìn)程唯一單例、線程唯一單例节沦、集群唯一單例键思、多例等擴(kuò)展知識(shí)點(diǎn),這一部分在實(shí)際的開發(fā)中并不會(huì)被用到甫贯,但是可以擴(kuò)展你的思路吼鳞、鍛煉你的邏輯思維。這里我就不帶你回顧了叫搁,你可以自己回憶一下赖条。
2. 工廠模式
工廠模式包括簡(jiǎn)單工廠、工廠方法常熙、抽象工廠這 3 種細(xì)分模式纬乍。其中,簡(jiǎn)單工廠和工廠方法比較常用裸卫,抽象工廠的應(yīng)用場(chǎng)景比較特殊仿贬,所以很少用到,不是我們學(xué)習(xí)的重點(diǎn)墓贿。
工廠模式用來(lái)創(chuàng)建不同但是相關(guān)類型的對(duì)象(繼承同一父類或者接口的一組子類)茧泪,由給定的參數(shù)來(lái)決定創(chuàng)建哪種類型的對(duì)象。實(shí)際上聋袋,如果創(chuàng)建對(duì)象的邏輯并不復(fù)雜队伟,那我們直接通過(guò) new 來(lái)創(chuàng)建對(duì)象就可以了,不需要使用工廠模式幽勒。當(dāng)創(chuàng)建邏輯比較復(fù)雜嗜侮,是一個(gè)“大工程”的時(shí)候,我們就考慮使用工廠模式,封裝對(duì)象的創(chuàng)建過(guò)程锈颗,將對(duì)象的創(chuàng)建和使用相分離顷霹。
當(dāng)每個(gè)對(duì)象的創(chuàng)建邏輯都比較簡(jiǎn)單的時(shí)候,我推薦使用簡(jiǎn)單工廠模式击吱,將多個(gè)對(duì)象的創(chuàng)建邏輯放到一個(gè)工廠類中淋淀。當(dāng)每個(gè)對(duì)象的創(chuàng)建邏輯都比較復(fù)雜的時(shí)候,為了避免設(shè)計(jì)一個(gè)過(guò)于龐大的工廠類覆醇,我們推薦使用工廠方法模式朵纷,將創(chuàng)建邏輯拆分得更細(xì),每個(gè)對(duì)象的創(chuàng)建邏輯獨(dú)立到各自的工廠類中永脓。
詳細(xì)點(diǎn)說(shuō)袍辞,工廠模式的作用有下面 4 個(gè),這也是判斷要不要使用工廠模式最本質(zhì)的參考標(biāo)準(zhǔn)憨奸。
- 封裝變化:創(chuàng)建邏輯有可能變化革屠,封裝成工廠類之后凿试,創(chuàng)建邏輯的變更對(duì)調(diào)用者透明排宰。
- 代碼復(fù)用:創(chuàng)建代碼抽離到獨(dú)立的工廠類之后可以復(fù)用。
- 隔離復(fù)雜性:封裝復(fù)雜的創(chuàng)建邏輯那婉,調(diào)用者無(wú)需了解如何創(chuàng)建對(duì)象板甘。控制復(fù)雜度:將創(chuàng)建代碼抽離出來(lái)详炬,讓原本的函數(shù)或類職責(zé)更單一盐类,代碼更簡(jiǎn)潔。
除此之外呛谜,我們還講了工廠模式一個(gè)非常經(jīng)典的應(yīng)用場(chǎng)景:依賴注入框架在跳,比如 Spring IOC、Google Guice隐岛,它用來(lái)集中創(chuàng)建猫妙、組裝、管理對(duì)象聚凹,跟具體業(yè)務(wù)代碼解耦割坠,讓程序員聚焦在業(yè)務(wù)代碼的開發(fā)上。DI 框架已經(jīng)成為了我們平時(shí)開發(fā)的必備框架妒牙,在專欄中彼哼,我還帶你實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 DI 框架,你可以再回過(guò)頭去看看湘今。
3. 建造者模式
建造者模式用來(lái)創(chuàng)建復(fù)雜對(duì)象敢朱,可以通過(guò)設(shè)置不同的可選參數(shù),“定制化”地創(chuàng)建不同的對(duì)象。建造者模式的原理和實(shí)現(xiàn)比較簡(jiǎn)單蔫饰,重點(diǎn)是掌握應(yīng)用場(chǎng)景琅豆,避免過(guò)度使用。
如果一個(gè)類中有很多屬性篓吁,為了避免構(gòu)造函數(shù)的參數(shù)列表過(guò)長(zhǎng)茫因,影響代碼的可讀性和易用性,我們可以通過(guò)構(gòu)造函數(shù)配合 set() 方法來(lái)解決杖剪。但是冻押,如果存在下面情況中的任意一種,我們就要考慮使用建造者模式了盛嘿。
- 我們把類的必填屬性放到構(gòu)造函數(shù)中洛巢,強(qiáng)制創(chuàng)建對(duì)象的時(shí)候就設(shè)置。如果必填的屬性有很多次兆,把這些必填屬性都放到構(gòu)造函數(shù)中設(shè)置稿茉,那構(gòu)造函數(shù)就又會(huì)出現(xiàn)參數(shù)列表很長(zhǎng)的問(wèn)題。如果我們把必填屬性通過(guò) set() 方法設(shè)置芥炭,那校驗(yàn)這些必填屬性是否已經(jīng)填寫的邏輯就無(wú)處安放了漓库。
- 如果類的屬性之間有一定的依賴關(guān)系或者約束條件,我們繼續(xù)使用構(gòu)造函數(shù)配合 set() 方法的設(shè)計(jì)思路园蝠,那這些依賴關(guān)系或約束條件的校驗(yàn)邏輯就無(wú)處安放了渺蒿。
- 如果我們希望創(chuàng)建不可變對(duì)象,也就是說(shuō)彪薛,對(duì)象在創(chuàng)建好之后茂装,就不能再修改內(nèi)部的屬性值,要實(shí)現(xiàn)這個(gè)功能善延,我們就不能在類中暴露 set() 方法少态。構(gòu)造函數(shù)配合 set() 方法來(lái)設(shè)置屬性值的方式就不適用了。
4. 原型模式
如果對(duì)象的創(chuàng)建成本比較大易遣,而同一個(gè)類的不同對(duì)象之間差別不大(大部分字段都相同)彼妻,在這種情況下,我們可以利用對(duì)已有對(duì)象(原型)進(jìn)行復(fù)制(或者叫拷貝)的方式训挡,來(lái)創(chuàng)建新對(duì)象澳骤,以達(dá)到節(jié)省創(chuàng)建時(shí)間的目的。這種基于原型來(lái)創(chuàng)建對(duì)象的方式就叫作原型模式澜薄。
原型模式有兩種實(shí)現(xiàn)方法为肮,深拷貝和淺拷貝。淺拷貝只會(huì)復(fù)制對(duì)象中基本數(shù)據(jù)類型數(shù)據(jù)和引用對(duì)象的內(nèi)存地址肤京,不會(huì)遞歸地復(fù)制引用對(duì)象颊艳,以及引用對(duì)象的引用對(duì)象……而深拷貝得到的是一份完完全全獨(dú)立的對(duì)象茅特。所以,深拷貝比起淺拷貝來(lái)說(shuō)棋枕,更加耗時(shí)白修,更加耗內(nèi)存空間。
如果要拷貝的對(duì)象是不可變對(duì)象重斑,淺拷貝共享不可變對(duì)象是沒(méi)問(wèn)題的兵睛,但對(duì)于可變對(duì)象來(lái)說(shuō),淺拷貝得到的對(duì)象和原始對(duì)象會(huì)共享部分?jǐn)?shù)據(jù)窥浪,就有可能出現(xiàn)數(shù)據(jù)被修改的風(fēng)險(xiǎn)祖很,也就變得復(fù)雜多了。操作非常耗時(shí)的情況下漾脂,我們比較推薦使用淺拷貝假颇,否則,沒(méi)有充分的理由骨稿,不要為了一點(diǎn)點(diǎn)的性能提升而使用淺拷貝笨鸡。
二、結(jié)構(gòu)型設(shè)計(jì)模式
結(jié)構(gòu)型模式主要總結(jié)了一些類或?qū)ο蠼M合在一起的經(jīng)典結(jié)構(gòu)坦冠,這些經(jīng)典的結(jié)構(gòu)可以解決特定應(yīng)用場(chǎng)景的問(wèn)題形耗。結(jié)構(gòu)型模式包括:代理模式、橋接模式蓝牲、裝飾器模式趟脂、適配器模式泰讽、門面模式例衍、組合模式、享元模式已卸。
1. 代理模式
代理模式在不改變?cè)碱惤涌诘臈l件下佛玄,為原始類定義一個(gè)代理類,主要目的是控制訪問(wèn)累澡,而非加強(qiáng)功能梦抢,這是它跟裝飾器模式最大的不同。一般情況下愧哟,我們讓代理類和原始類實(shí)現(xiàn)同樣的接口掀宋。但是甩牺,如果原始類并沒(méi)有定義接口,并且原始類代碼并不是我們開發(fā)維護(hù)的。在這種情況下余赢,我們可以通過(guò)讓代理類繼承原始類的方法來(lái)實(shí)現(xiàn)代理模式。
靜態(tài)代理需要針對(duì)每個(gè)類都創(chuàng)建一個(gè)代理類帐我,并且每個(gè)代理類中的代碼都有點(diǎn)像模板式的“重復(fù)”代碼符糊,增加了維護(hù)成本和開發(fā)成本。對(duì)于靜態(tài)代理存在的問(wèn)題,我們可以通過(guò)動(dòng)態(tài)代理來(lái)解決旅东。我們不事先為每個(gè)原始類編寫代理類灭抑,而是在運(yùn)行的時(shí)候動(dòng)態(tài)地創(chuàng)建原始類對(duì)應(yīng)的代理類,然后在系統(tǒng)中用代理類替換掉原始類抵代。
代理模式常用在業(yè)務(wù)系統(tǒng)中開發(fā)一些非功能性需求腾节,比如:監(jiān)控、統(tǒng)計(jì)荤牍、鑒權(quán)禀倔、限流、事務(wù)参淫、冪等救湖、日志。我們將這些附加功能與業(yè)務(wù)功能解耦涎才,放到代理類統(tǒng)一處理鞋既,讓程序員只需要關(guān)注業(yè)務(wù)方面的開發(fā)。除此之外耍铜,代理模式還可以用在 RPC邑闺、緩存等應(yīng)用場(chǎng)景中。
2. 橋接模式
橋接模式的代碼實(shí)現(xiàn)非常簡(jiǎn)單棕兼,但是理解起來(lái)稍微有點(diǎn)難度陡舅,并且應(yīng)用場(chǎng)景也比較局限,所以伴挚,相對(duì)來(lái)說(shuō)靶衍,橋接模式在實(shí)際的項(xiàng)目中并沒(méi)有那么常用,你只需要簡(jiǎn)單了解茎芋,見到能認(rèn)識(shí)就可以了颅眶,并不是我們學(xué)習(xí)的重點(diǎn)。
橋接模式有兩種理解方式田弥。第一種理解方式是“將抽象和實(shí)現(xiàn)解耦涛酗,讓它們能獨(dú)立開發(fā)”。這種理解方式比較特別偷厦,應(yīng)用場(chǎng)景也不多商叹。另一種理解方式更加簡(jiǎn)單,等同于“組合優(yōu)于繼承”設(shè)計(jì)原則只泼,這種理解方式更加通用剖笙,應(yīng)用場(chǎng)景比較多。不管是哪種理解方式辜妓,它們的代碼結(jié)構(gòu)都是相同的枯途,都是一種類之間的組合關(guān)系忌怎。
對(duì)于第一種理解方式,弄懂定義中“抽象”和“實(shí)現(xiàn)”兩個(gè)概念酪夷,是理解它的關(guān)鍵榴啸。定義中的“抽象”,指的并非“抽象類”或“接口”晚岭,而是被抽象出來(lái)的一套“類庫(kù)”鸥印,它只包含骨架代碼,真正的業(yè)務(wù)邏輯需要委派給定義中的“實(shí)現(xiàn)”來(lái)完成坦报。而定義中的“實(shí)現(xiàn)”库说,也并非“接口的實(shí)現(xiàn)類”,而是的一套獨(dú)立的“類庫(kù)”片择∏钡模“抽象”和“實(shí)現(xiàn)”獨(dú)立開發(fā),通過(guò)對(duì)象之間的組合關(guān)系組裝在一起字管。
3. 裝飾器模式
裝飾器模式主要解決繼承關(guān)系過(guò)于復(fù)雜的問(wèn)題啰挪,通過(guò)組合來(lái)替代繼承,給原始類添加增強(qiáng)功能嘲叔。這也是判斷是否該用裝飾器模式的一個(gè)重要的依據(jù)亡呵。除此之外,裝飾器模式還有一個(gè)特點(diǎn)硫戈,那就是可以對(duì)原始類嵌套使用多個(gè)裝飾器锰什。為了滿足這樣的需求,在設(shè)計(jì)的時(shí)候丁逝,裝飾器類需要跟原始類繼承相同的抽象類或者接口汁胆。
4. 適配器模式
代理模式、裝飾器模式提供的都是跟原始類相同的接口果港,而適配器提供跟原始類不同的接口沦泌。適配器模式是用來(lái)做適配的糊昙,它將不兼容的接口轉(zhuǎn)換為可兼容的接口辛掠,讓原本由于接口不兼容而不能一起工作的類可以一起工作。適配器模式有兩種實(shí)現(xiàn)方式:類適配器和對(duì)象適配器释牺。其中萝衩,類適配器使用繼承關(guān)系來(lái)實(shí)現(xiàn),對(duì)象適配器使用組合關(guān)系來(lái)實(shí)現(xiàn)没咙。
適配器模式是一種事后的補(bǔ)救策略猩谊,用來(lái)補(bǔ)救設(shè)計(jì)上的缺陷。應(yīng)用這種模式算是“無(wú)奈之舉”祭刚。如果在設(shè)計(jì)初期牌捷,我們就能規(guī)避接口不兼容的問(wèn)題墙牌,那這種模式就無(wú)用武之地了。在實(shí)際的開發(fā)中暗甥,什么情況下才會(huì)出現(xiàn)接口不兼容呢喜滨?我總結(jié)下了下面這 5 種場(chǎng)景:
- 封裝有缺陷的接口設(shè)計(jì)
- 統(tǒng)一多個(gè)類的接口設(shè)計(jì)
- 替換依賴的外部系統(tǒng)
- 兼容老版本接口
- 適配不同格式的數(shù)據(jù)
5. 門面模式
門面模式原理、實(shí)現(xiàn)都非常簡(jiǎn)單撤防,應(yīng)用場(chǎng)景比較明確虽风。它通過(guò)封裝細(xì)粒度的接口,提供組合各個(gè)細(xì)粒度接口的高層次接口寄月,來(lái)提高接口的易用性辜膝,或者解決性能、分布式事務(wù)等問(wèn)題漾肮。
6. 組合模式
組合模式跟我們之前講的面向?qū)ο笤O(shè)計(jì)中的“組合關(guān)系(通過(guò)組合來(lái)組裝兩個(gè)類)”厂抖,完全是兩碼事。這里講的“組合模式”克懊,主要是用來(lái)處理樹形結(jié)構(gòu)數(shù)據(jù)验游。正因?yàn)槠鋺?yīng)用場(chǎng)景的特殊性,數(shù)據(jù)必須能表示成樹形結(jié)構(gòu)保檐,這也導(dǎo)致了這種模式在實(shí)際的項(xiàng)目開發(fā)中并不那么常用耕蝉。但是,一旦數(shù)據(jù)滿足樹形結(jié)構(gòu)夜只,應(yīng)用這種模式就能發(fā)揮很大的作用垒在,能讓代碼變得非常簡(jiǎn)潔。
組合模式的設(shè)計(jì)思路扔亥,與其說(shuō)是一種設(shè)計(jì)模式场躯,倒不如說(shuō)是對(duì)業(yè)務(wù)場(chǎng)景的一種數(shù)據(jù)結(jié)構(gòu)和算法的抽象。其中旅挤,數(shù)據(jù)可以表示成樹這種數(shù)據(jù)結(jié)構(gòu)踢关,業(yè)務(wù)需求可以通過(guò)在樹上的遞歸遍歷算法來(lái)實(shí)現(xiàn)。組合模式粘茄,將一組對(duì)象組織成樹形結(jié)構(gòu)签舞,將單個(gè)對(duì)象和組合對(duì)象都看作樹中的節(jié)點(diǎn),以統(tǒng)一處理邏輯柒瓣,并且它利用樹形結(jié)構(gòu)的特點(diǎn)儒搭,遞歸地處理每個(gè)子樹,依次簡(jiǎn)化代碼實(shí)現(xiàn)芙贫。
7. 享元模式
所謂“享元”搂鲫,顧名思義就是被共享的單元。享元模式的意圖是復(fù)用對(duì)象磺平,節(jié)省內(nèi)存魂仍,前提是享元對(duì)象是不可變對(duì)象拐辽。
具體來(lái)講,當(dāng)一個(gè)系統(tǒng)中存在大量重復(fù)對(duì)象的時(shí)候擦酌,我們就可以利用享元模式薛训,將對(duì)象設(shè)計(jì)成享元,在內(nèi)存中只保留一份實(shí)例仑氛,供多處代碼引用乙埃,這樣可以減少內(nèi)存中對(duì)象的數(shù)量,以起到節(jié)省內(nèi)存的目的锯岖。實(shí)際上介袜,不僅僅相同對(duì)象可以設(shè)計(jì)成享元,對(duì)于相似對(duì)象出吹,我們也可以將這些對(duì)象中相同的部分(字段)遇伞,提取出來(lái)設(shè)計(jì)成享元,讓這些大量相似對(duì)象引用這些享元捶牢。
三鸠珠、行為型設(shè)計(jì)模式
我們知道,創(chuàng)建型設(shè)計(jì)模式主要解決“對(duì)象的創(chuàng)建”問(wèn)題秋麸,結(jié)構(gòu)型設(shè)計(jì)模式主要解決“類或?qū)ο蟮慕M合”問(wèn)題渐排,那行為型設(shè)計(jì)模式主要解決的就是“類或?qū)ο笾g的交互”問(wèn)題。行為型模式比較多灸蟆,有 11 種驯耻,它們分別是:觀察者模式、模板模式炒考、策略模式可缚、職責(zé)鏈模式、迭代器模式斋枢、狀態(tài)模式帘靡、訪問(wèn)者模式、備忘錄模式瓤帚、命令模式描姚、解釋器模式、中介模式缘滥。
1. 觀察者
觀察者模式將觀察者和被觀察者代碼解耦轰胁。觀察者模式的應(yīng)用場(chǎng)景非常廣泛,小到代碼層面的解耦朝扼,大到架構(gòu)層面的系統(tǒng)解耦,再或者一些產(chǎn)品的設(shè)計(jì)思路霎肯,都有這種模式的影子擎颖,比如榛斯,郵件訂閱、RSS Feeds搂捧,本質(zhì)上都是觀察者模式驮俗。
不同的應(yīng)用場(chǎng)景和需求下,這個(gè)模式也有截然不同的實(shí)現(xiàn)方式:有同步阻塞的實(shí)現(xiàn)方式允跑,也有異步非阻塞的實(shí)現(xiàn)方式王凑;有進(jìn)程內(nèi)的實(shí)現(xiàn)方式,也有跨進(jìn)程的實(shí)現(xiàn)方式聋丝。同步阻塞是最經(jīng)典的實(shí)現(xiàn)方式索烹,主要是為了代碼解耦;異步非阻塞除了能實(shí)現(xiàn)代碼解耦之外弱睦,還能提高代碼的執(zhí)行效率百姓;進(jìn)程間的觀察者模式解耦更加徹底,一般是基于消息隊(duì)列來(lái)實(shí)現(xiàn)况木,用來(lái)實(shí)現(xiàn)不同進(jìn)程間的被觀察者和觀察者之間的交互垒拢。
框架的作用有隱藏實(shí)現(xiàn)細(xì)節(jié),降低開發(fā)難度火惊,實(shí)現(xiàn)代碼復(fù)用求类,解耦業(yè)務(wù)與非業(yè)務(wù)代碼,讓程序員聚焦業(yè)務(wù)開發(fā)屹耐。針對(duì)異步非阻塞觀察者模式仑嗅,我們也可以將它抽象成 EventBus 框架來(lái)達(dá)到這樣的效果。EventBus 翻譯為“事件總線”张症,它提供了實(shí)現(xiàn)觀察者模式的骨架代碼仓技。我們可以基于此框架非常容易地在自己的業(yè)務(wù)場(chǎng)景中實(shí)現(xiàn)觀察者模式,不需要從零開始開發(fā)俗他。
2. 模板模式
模板方法模式在一個(gè)方法中定義一個(gè)算法骨架脖捻,并將某些步驟推遲到子類中實(shí)現(xiàn)。模板方法模式可以讓子類在不改變算法整體結(jié)構(gòu)的情況下兆衅,重新定義算法中的某些步驟地沮。這里的“算法”,我們可以理解為廣義上的“業(yè)務(wù)邏輯”羡亩,并不特指數(shù)據(jù)結(jié)構(gòu)和算法中的“算法”摩疑。這里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”畏铆,這也是模板方法模式名字的由來(lái)雷袋。
模板模式有兩大作用:復(fù)用和擴(kuò)展。其中復(fù)用指的是辞居,所有的子類可以復(fù)用父類中提供的模板方法的代碼楷怒。擴(kuò)展指的是蛋勺,框架通過(guò)模板模式提供功能擴(kuò)展點(diǎn),讓框架用戶可以在不修改框架源碼的情況下鸠删,基于擴(kuò)展點(diǎn)定制化框架的功能抱完。
除此之外,我們還講到回調(diào)刃泡。它跟模板模式具有相同的作用:代碼復(fù)用和擴(kuò)展巧娱。在一些框架、類庫(kù)烘贴、組件等的設(shè)計(jì)中經(jīng)常會(huì)用到禁添,比如 JdbcTemplate 就是用了回調(diào)。
相對(duì)于普通的函數(shù)調(diào)用庙楚,回調(diào)是一種雙向調(diào)用關(guān)系上荡。A 類事先注冊(cè)某個(gè)函數(shù) F 到 B 類,A 類在調(diào)用 B 類的 P 函數(shù)的時(shí)候馒闷,B 類反過(guò)來(lái)調(diào)用 A 類注冊(cè)給它的 F 函數(shù)酪捡。這里的 F 函數(shù)就是“回調(diào)函數(shù)”。A 調(diào)用 B纳账,B 反過(guò)來(lái)又調(diào)用 A逛薇,這種調(diào)用機(jī)制就叫作“回調(diào)”。
回調(diào)可以細(xì)分為同步回調(diào)和異步回調(diào)疏虫。從應(yīng)用場(chǎng)景上來(lái)看永罚,同步回調(diào)看起來(lái)更像模板模式,異步回調(diào)看起來(lái)更像觀察者模式卧秘∧馗ぃ回調(diào)跟模板模式的區(qū)別,更多的是在代碼實(shí)現(xiàn)上翅敌,而非應(yīng)用場(chǎng)景上羞福。回調(diào)基于組合關(guān)系來(lái)實(shí)現(xiàn)蚯涮,模板模式基于繼承關(guān)系來(lái)實(shí)現(xiàn)治专。回調(diào)比模板模式更加靈活遭顶。
3. 策略模式
策略模式定義一組算法類张峰,將每個(gè)算法分別封裝起來(lái),讓它們可以互相替換棒旗。策略模式可以使算法的變化獨(dú)立于使用它們的客戶端(這里的客戶端代指使用算法的代碼)喘批。策略模式用來(lái)解耦策略的定義、創(chuàng)建、使用谤祖。實(shí)際上婿滓,一個(gè)完整的策略模式就是由這三個(gè)部分組成的老速。
策略類的定義比較簡(jiǎn)單粥喜,包含一個(gè)策略接口和一組實(shí)現(xiàn)這個(gè)接口的策略類。策略的創(chuàng)建由工廠類來(lái)完成橘券,封裝策略創(chuàng)建的細(xì)節(jié)额湘。策略模式包含一組策略可選,客戶端代碼選擇使用哪個(gè)策略旁舰,有兩種確定方法:編譯時(shí)靜態(tài)確定和運(yùn)行時(shí)動(dòng)態(tài)確定锋华。其中,“運(yùn)行時(shí)動(dòng)態(tài)確定”才是策略模式最典型的應(yīng)用場(chǎng)景箭窜。
在實(shí)際的項(xiàng)目開發(fā)中毯焕,策略模式也比較常用。最常見的應(yīng)用場(chǎng)景是磺樱,利用它來(lái)避免冗長(zhǎng)的 if-else 或 switch 分支判斷纳猫。不過(guò),它的作用還不止如此竹捉。它也可以像模板模式那樣芜辕,提供框架的擴(kuò)展點(diǎn)等等。實(shí)際上块差,策略模式主要的作用還是解耦策略的定義侵续、創(chuàng)建和使用,控制代碼的復(fù)雜度憨闰,讓每個(gè)部分都不至于過(guò)于復(fù)雜状蜗、代碼量過(guò)多档冬。除此之外勾习,對(duì)于復(fù)雜代碼來(lái)說(shuō),策略模式還能讓其滿足開閉原則竿奏,添加新策略的時(shí)候训裆,最小化眶根、集中化代碼改動(dòng),減少引入 bug 的風(fēng)險(xiǎn)边琉。
4. 職責(zé)鏈模式
在職責(zé)鏈模式中属百,多個(gè)處理器依次處理同一個(gè)請(qǐng)求。一個(gè)請(qǐng)求先經(jīng)過(guò) A 處理器處理变姨,然后再把請(qǐng)求傳遞給 B 處理器族扰,B 處理器處理完后再傳遞給 C 處理器,以此類推,形成一個(gè)鏈條渔呵。鏈條上的每個(gè)處理器各自承擔(dān)各自的處理職責(zé)怒竿,所以叫作職責(zé)鏈模式。
在 GoF 的定義中扩氢,一旦某個(gè)處理器能處理這個(gè)請(qǐng)求耕驰,就不會(huì)繼續(xù)將請(qǐng)求傳遞給后續(xù)的處理器了。當(dāng)然录豺,在實(shí)際的開發(fā)中朦肘,也存在對(duì)這個(gè)模式的變體,那就是請(qǐng)求不會(huì)中途終止傳遞双饥,而是會(huì)被所有的處理器都處理一遍媒抠。
職責(zé)鏈模式常用在框架開發(fā)中,用來(lái)實(shí)現(xiàn)過(guò)濾器咏花、攔截器功能趴生,讓框架的使用者在不需要修改框架源碼的情況下,添加新的過(guò)濾昏翰、攔截功能苍匆。這也體現(xiàn)了之前講到的對(duì)擴(kuò)展開放、對(duì)修改關(guān)閉的設(shè)計(jì)原則矩父。
5. 迭代器模式
迭代器模式也叫游標(biāo)模式锉桑,它用來(lái)遍歷集合對(duì)象。這里說(shuō)的“集合對(duì)象”窍株,我們也可以叫“容器”“聚合對(duì)象”民轴,實(shí)際上就是包含一組對(duì)象的對(duì)象,比如球订,數(shù)組、鏈表冒滩、樹因苹、圖智蝠、跳表漆撞。迭代器模式主要作用是解耦容器代碼和遍歷代碼。大部分編程語(yǔ)言都提供了現(xiàn)成的迭代器可以使用,我們不需要從零開始開發(fā)。
遍歷集合一般有三種方式:for 循環(huán)、foreach 循環(huán)、迭代器遍歷甜无。后兩種本質(zhì)上屬于一種铜邮,都可以看作迭代器遍歷已旧。相對(duì)于 for 循環(huán)遍歷秸苗,利用迭代器來(lái)遍歷有 3 個(gè)優(yōu)勢(shì):
- 迭代器模式封裝集合內(nèi)部的復(fù)雜數(shù)據(jù)結(jié)構(gòu),開發(fā)者不需要了解如何遍歷运褪,直接使用容器提供的迭代器即可惊楼;
- 迭代器模式將集合對(duì)象的遍歷操作從集合類中拆分出來(lái),放到迭代器類中秸讹,讓兩者的職責(zé)更加單一檀咙;
- 迭代器模式讓添加新的遍歷算法更加容易,更符合開閉原則璃诀。除此之外弧可,因?yàn)榈鞫紝?shí)現(xiàn)自相同的接口,在開發(fā)中劣欢,基于接口而非實(shí)現(xiàn)編程棕诵,替換迭代器也變得更加容易。
在通過(guò)迭代器來(lái)遍歷集合元素的同時(shí)氧秘,增加或者刪除集合中的元素年鸳,有可能會(huì)導(dǎo)致某個(gè)元素被重復(fù)遍歷或遍歷不到。針對(duì)這個(gè)問(wèn)題丸相,有兩種比較干脆利索的解決方案搔确,來(lái)避免出現(xiàn)這種不可預(yù)期的運(yùn)行結(jié)果。一種是遍歷的時(shí)候不允許增刪元素灭忠,另一種是增刪元素之后讓遍歷報(bào)錯(cuò)膳算。第一種解決方案比較難實(shí)現(xiàn),因?yàn)楹茈y確定迭代器使用結(jié)束的時(shí)間點(diǎn)弛作。第二種解決方案更加合理涕蜂,Java 語(yǔ)言就是采用的這種解決方案。增刪元素之后映琳,我們選擇 fail-fast 解決方式机隙,讓遍歷操作直接拋出運(yùn)行時(shí)異常蜘拉。
6. 狀態(tài)模式
狀態(tài)模式一般用來(lái)實(shí)現(xiàn)狀態(tài)機(jī),而狀態(tài)機(jī)常用在游戲有鹿、工作流引擎等系統(tǒng)開發(fā)中旭旭。狀態(tài)機(jī)又叫有限狀態(tài)機(jī),它由 3 個(gè)部分組成:狀態(tài)葱跋、事件持寄、動(dòng)作。其中娱俺,事件也稱為轉(zhuǎn)移條件稍味。事件觸發(fā)狀態(tài)的轉(zhuǎn)移及動(dòng)作的執(zhí)行。不過(guò)荠卷,動(dòng)作不是必須的模庐,也可能只轉(zhuǎn)移狀態(tài),不執(zhí)行任何動(dòng)作僵朗。
針對(duì)狀態(tài)機(jī)赖欣,我們總結(jié)了三種實(shí)現(xiàn)方式。
第一種實(shí)現(xiàn)方式叫分支邏輯法验庙。利用 if-else 或者 switch-case 分支邏輯,參照狀態(tài)轉(zhuǎn)移圖社牲,將每一個(gè)狀態(tài)轉(zhuǎn)移原模原樣地直譯成代碼粪薛。對(duì)于簡(jiǎn)單的狀態(tài)機(jī)來(lái)說(shuō),這種實(shí)現(xiàn)方式最簡(jiǎn)單搏恤、最直接违寿,是首選。
第二種實(shí)現(xiàn)方式叫查表法熟空。對(duì)于狀態(tài)很多藤巢、狀態(tài)轉(zhuǎn)移比較復(fù)雜的狀態(tài)機(jī)來(lái)說(shuō),查表法比較合適息罗。通過(guò)二維數(shù)組來(lái)表示狀態(tài)轉(zhuǎn)移圖掂咒,能極大地提高代碼的可讀性和可維護(hù)性。
第三種實(shí)現(xiàn)方式就是利用狀態(tài)模式迈喉。對(duì)于狀態(tài)并不多绍刮、狀態(tài)轉(zhuǎn)移也比較簡(jiǎn)單,但事件觸發(fā)執(zhí)行的動(dòng)作包含的業(yè)務(wù)邏輯可能比較復(fù)雜的狀態(tài)機(jī)來(lái)說(shuō)挨摸,我們首選這種實(shí)現(xiàn)方式孩革。
7. 訪問(wèn)者模式
訪問(wèn)者模式允許一個(gè)或者多個(gè)操作應(yīng)用到一組對(duì)象上,設(shè)計(jì)意圖是解耦操作和對(duì)象本身得运,保持類職責(zé)單一膝蜈、滿足開閉原則以及應(yīng)對(duì)代碼的復(fù)雜性锅移。
對(duì)于訪問(wèn)者模式,學(xué)習(xí)的主要難點(diǎn)在代碼實(shí)現(xiàn)饱搏。而代碼實(shí)現(xiàn)比較復(fù)雜的主要原因是帆啃,函數(shù)重載在大部分面向?qū)ο缶幊陶Z(yǔ)言中是靜態(tài)綁定的。也就是說(shuō)窍帝,調(diào)用類的哪個(gè)重載函數(shù)努潘,是在編譯期間,由參數(shù)的聲明類型決定的坤学,而非運(yùn)行時(shí)疯坤,根據(jù)參數(shù)的實(shí)際類型決定的。除此之外深浮,我們還講到 Double Disptach压怠。如果某種語(yǔ)言支持 Double Dispatch,那就不需要訪問(wèn)者模式了飞苇。
正是因?yàn)榇a實(shí)現(xiàn)難理解菌瘫,所以,在項(xiàng)目中應(yīng)用這種模式布卡,會(huì)導(dǎo)致代碼的可讀性比較差雨让。如果你的同事不了解這種設(shè)計(jì)模式,可能就會(huì)讀不懂忿等、維護(hù)不了你寫的代碼栖忠。所以,除非不得已贸街,不要使用這種模式庵寞。
8. 備忘錄模式
備忘錄模式也叫快照模式,具體來(lái)說(shuō)薛匪,就是在不違背封裝原則的前提下捐川,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)逸尖,以便之后恢復(fù)對(duì)象為先前的狀態(tài)古沥。這個(gè)模式的定義表達(dá)了兩部分內(nèi)容:一部分是,存儲(chǔ)副本以便后期恢復(fù)冷溶;另一部分是渐白,要在不違背封裝原則的前提下,進(jìn)行對(duì)象的備份和恢復(fù)逞频。
備忘錄模式的應(yīng)用場(chǎng)景也比較明確和有限纯衍,主要用來(lái)防丟失、撤銷苗胀、恢復(fù)等襟诸。它跟平時(shí)我們常說(shuō)的“備份”很相似瓦堵。兩者的主要區(qū)別在于,備忘錄模式更側(cè)重于代碼的設(shè)計(jì)和實(shí)現(xiàn)歌亲,備份更側(cè)重架構(gòu)設(shè)計(jì)或產(chǎn)品設(shè)計(jì)菇用。
對(duì)于大對(duì)象的備份來(lái)說(shuō),備份占用的存儲(chǔ)空間會(huì)比較大陷揪,備份和恢復(fù)的耗時(shí)會(huì)比較長(zhǎng)惋鸥。針對(duì)這個(gè)問(wèn)題,不同的業(yè)務(wù)場(chǎng)景有不同的處理方式悍缠。比如卦绣,只備份必要的恢復(fù)信息,結(jié)合最新的數(shù)據(jù)來(lái)恢復(fù)飞蚓;再比如滤港,全量備份和增量備份相結(jié)合,低頻全量備份趴拧,高頻增量備份溅漾,兩者結(jié)合來(lái)做恢復(fù)。
9. 命令模式
命令模式在平時(shí)工作中并不常用著榴,你稍微了解一下就可以添履。
落實(shí)到編碼實(shí)現(xiàn),命令模式用到最核心的實(shí)現(xiàn)手段兄渺,就是將函數(shù)封裝成對(duì)象缝龄。我們知道,在大部分編程語(yǔ)言中挂谍,函數(shù)是沒(méi)法作為參數(shù)傳遞給其他函數(shù)的,也沒(méi)法賦值給變量瞎饲。借助命令模式口叙,我們將函數(shù)封裝成對(duì)象,這樣就可以實(shí)現(xiàn)把函數(shù)像對(duì)象一樣使用嗅战。
命令模式的主要作用和應(yīng)用場(chǎng)景妄田,是用來(lái)控制命令的執(zhí)行,比如驮捍,異步疟呐、延遲、排隊(duì)執(zhí)行命令东且、撤銷重做命令启具、存儲(chǔ)命令、給命令記錄日志等珊泳,這才是命令模式能發(fā)揮獨(dú)一無(wú)二作用的地方鲁冯。
10. 解釋器模式
解釋器模式為某個(gè)語(yǔ)言定義它的語(yǔ)法(或者叫文法)表示拷沸,并定義一個(gè)解釋器用來(lái)處理這個(gè)語(yǔ)法。實(shí)際上薯演,這里的“語(yǔ)言”不僅僅指我們平時(shí)說(shuō)的中撞芍、英、日跨扮、法等各種語(yǔ)言序无。從廣義上來(lái)講,只要是能承載信息的載體衡创,我們都可以稱之為“語(yǔ)言”帝嗡,比如,古代的結(jié)繩記事钧汹、盲文丈探、啞語(yǔ)、摩斯密碼等拔莱。
要想了解“語(yǔ)言”要表達(dá)的信息碗降,我們就必須定義相應(yīng)的語(yǔ)法規(guī)則。這樣塘秦,書寫者就可以根據(jù)語(yǔ)法規(guī)則來(lái)書寫“句子”(專業(yè)點(diǎn)的叫法應(yīng)該是“表達(dá)式”)讼渊,閱讀者根據(jù)語(yǔ)法規(guī)則來(lái)閱讀“句子”,這樣才能做到信息的正確傳遞尊剔。而我們要講的解釋器模式爪幻,其實(shí)就是用來(lái)實(shí)現(xiàn)根據(jù)語(yǔ)法規(guī)則解讀“句子”的解釋器。
解釋器模式的代碼實(shí)現(xiàn)比較靈活须误,沒(méi)有固定的模板挨稿。我們前面說(shuō)過(guò),應(yīng)用設(shè)計(jì)模式主要是應(yīng)對(duì)代碼的復(fù)雜性京痢,解釋器模式也不例外奶甘。它的代碼實(shí)現(xiàn)的核心思想,就是將語(yǔ)法解析的工作拆分到各個(gè)小類中祭椰,以此來(lái)避免大而全的解析類臭家。一般的做法是,將語(yǔ)法規(guī)則拆分一些小的獨(dú)立的單元方淤,然后對(duì)每個(gè)單元進(jìn)行解析钉赁,最終合并為對(duì)整個(gè)語(yǔ)法規(guī)則的解析。
11. 中介模式
中介模式的設(shè)計(jì)思想跟中間層很像携茂,通過(guò)引入中介這個(gè)中間層你踩,將一組對(duì)象之間的交互關(guān)系(或者說(shuō)依賴關(guān)系)從多對(duì)多(網(wǎng)狀關(guān)系)轉(zhuǎn)換為一對(duì)多(星狀關(guān)系)。原來(lái)一個(gè)對(duì)象要跟 n 個(gè)對(duì)象交互,現(xiàn)在只需要跟一個(gè)中介對(duì)象交互姓蜂,從而最小化對(duì)象之間的交互關(guān)系按厘,降低了代碼的復(fù)雜度,提高了代碼的可讀性和可維護(hù)性钱慢。
觀察者模式和中介模式都是為了實(shí)現(xiàn)參與者之間的解耦逮京,簡(jiǎn)化交互關(guān)系。兩者的不同在于應(yīng)用場(chǎng)景上束莫。在觀察者模式的應(yīng)用場(chǎng)景中懒棉,參與者之間的交互比較有條理,一般都是單向的览绿,一個(gè)參與者只有一個(gè)身份策严,要么是觀察者,要么是被觀察者饿敲。而在中介模式的應(yīng)用場(chǎng)景中妻导,參與者之間的交互關(guān)系錯(cuò)綜復(fù)雜,既可以是消息的發(fā)送者怀各、也可以同時(shí)是消息的接收者倔韭。
代碼Demo
23種設(shè)計(jì)模式的代碼實(shí)現(xiàn):https://github.com/lbshold/springboot/tree/master/design_demo