经典永驻,重温设计模式 |硬核!
|?導(dǎo)語(yǔ)?在軟工程中,設(shè)計(jì)模式(design pattern)是對(duì)軟件設(shè)計(jì)中普遍存在(反復(fù)出現(xiàn))的各種問(wèn)題,所提出的解決方案。這個(gè)術(shù)語(yǔ)是由埃里希·伽瑪(Erich Gamma)等人在1990年代從建筑設(shè)計(jì)領(lǐng)域引入到計(jì)算機(jī)科學(xué)的,設(shè)計(jì)模式是針對(duì)軟件設(shè)計(jì)中常見(jiàn)問(wèn)題的工具箱,其中的工具就是各種經(jīng)過(guò)實(shí)踐驗(yàn)證的解決方案。即使你從未遇到過(guò)這些問(wèn)題,了解模式仍然非常件有用,因?yàn)樗苤笇?dǎo)你如何使用面向?qū)ο蟮脑O(shè)計(jì)原則來(lái)解決各種問(wèn)題。
?
大家好,我是Alex,今天談一談設(shè)計(jì)模式,一名優(yōu)秀的開(kāi)發(fā),應(yīng)該多少都需要了解一些常用的設(shè)計(jì)模式和使用場(chǎng)景,讓我們一起來(lái)重溫一下那些年經(jīng)典設(shè)計(jì)模式;
?
本文主要內(nèi)容
?
?
為什么要掌握設(shè)計(jì)模式
?
歷史的教訓(xùn)
?
時(shí)間回到 20 世紀(jì) 80 年代,當(dāng)時(shí)的軟件行業(yè)正處于第二次軟件危機(jī)中。根本原因是,隨著軟件規(guī)模和復(fù)雜度的快速增長(zhǎng),如何高效高質(zhì)的構(gòu)建和維護(hù)這樣大規(guī)模的軟件成為了一大難題。無(wú)論是開(kāi)發(fā)何種軟件產(chǎn)品,成本和時(shí)間都最重要的兩個(gè)維度。較短的開(kāi)發(fā)時(shí)間意味著可比競(jìng)爭(zhēng)對(duì)手更早進(jìn)入市場(chǎng);較低的開(kāi)發(fā)成本意味著能夠留出更多營(yíng)銷(xiāo)資金,因此能更廣泛地覆蓋潛在客戶(hù)。
?
設(shè)計(jì)模式是銀彈嗎?
?
代碼復(fù)用是減少開(kāi)發(fā)成本,減低復(fù)雜度最常用的方式之一,這個(gè)想法表面看起來(lái)很棒,但實(shí)際上要讓已有代碼在全新的上下文中工作,通常還是需要付出額外努力的。組件間緊密的耦合、對(duì)具體類(lèi)而非接口的依賴(lài)和硬編碼的行為都會(huì)降低代碼的靈活性,使得復(fù)用這些代碼變得更加困難。設(shè)計(jì)模式目標(biāo)就是幫助軟件提高內(nèi)聚,減低耦合,使用設(shè)計(jì)模式是增加軟件組件靈活性并使其易于復(fù)用的方式之一。
?
變化是程序員生命中唯一不變的事情,客戶(hù)需求可能經(jīng)常會(huì)變,緊急上線的版本,要不要下次重構(gòu)一下,還是繼續(xù)打各種補(bǔ)丁, 技術(shù)債會(huì)越積越多,因此在設(shè)計(jì)程序架構(gòu)時(shí),所有有經(jīng)驗(yàn)的開(kāi)發(fā)者會(huì)盡量選擇支持未來(lái)任何可能變更的方式。可擴(kuò)展性成為了程序設(shè)計(jì)必須要考慮指標(biāo),而設(shè)計(jì)模式是可以借鑒的,成熟的優(yōu)化程序設(shè)計(jì)的解決方案;
?
總體來(lái)說(shuō),深刻理解設(shè)計(jì)模式會(huì)給我們帶來(lái)很多好處:
?
可以和面試官"暢談"設(shè)計(jì)模式相關(guān)問(wèn)題.
很多開(kāi)源軟件框架大量使用了設(shè)計(jì)模式,比如Linux系統(tǒng),Redis,Spring,C++STL等等,可以把幫你加快理解開(kāi)源軟件框架。
當(dāng)你寫(xiě)的代碼越來(lái)越優(yōu)美后,你的代碼鑒賞能力就會(huì)提高,對(duì)團(tuán)隊(duì)code review貢獻(xiàn)也會(huì)更大,在個(gè)人影響力也會(huì)提高。
你不會(huì)再畏手畏腳,你的工具箱里面工具很多后,可以幫助你,應(yīng)對(duì)各種大型項(xiàng)目的代碼設(shè)計(jì)和開(kāi)發(fā)。
每個(gè)領(lǐng)域都會(huì)一些成熟"套路", 編程也不例外,熟悉這些套路,可以更好方便交流和更快速地解決問(wèn)題;
?
為了更好理解設(shè)計(jì)模式,我們首先要理解一些重要的設(shè)計(jì)原則,而不是片面理解設(shè)計(jì)模式哪些模式名詞,要看清楚這背后的原理,這個(gè)才是最重要的。
?
代碼設(shè)計(jì)原則
?
代碼設(shè)計(jì)原則貫穿在整個(gè)設(shè)計(jì)模式之中,是理解其中的精華,本文討論了一些重要的設(shè)計(jì)原則,包括通用設(shè)計(jì)原則,DRY原則,KISS原則,SOLID原則等:
?
通用設(shè)計(jì)原則
?
隔離變化
找到程序中的變化內(nèi)容并將其與不變的內(nèi)容區(qū)分開(kāi),該原則的主要目的是將變更造成的影響最小化。
?
面向接口編程
面向接口進(jìn)行開(kāi)發(fā), 而不是面向?qū)崿F(xiàn);依賴(lài)于抽象類(lèi)型,而不是具體類(lèi),要求接口標(biāo)準(zhǔn)化設(shè)計(jì),只要對(duì)外的接口沒(méi)有變,內(nèi)部實(shí)現(xiàn)就可以任意變化,為以后留有更多優(yōu)化空間,方便以后更新迭代,可以說(shuō)這樣的設(shè)計(jì)是靈活的。
組合優(yōu)于繼承
繼承可能是類(lèi)之間最明顯、最簡(jiǎn)便的代碼復(fù)用方式。如果你有兩個(gè)代碼相同的類(lèi), 就可以為它們創(chuàng)建一個(gè)通用的基類(lèi),然后將相似的代碼移動(dòng)到其中。但繼承可能帶來(lái)的問(wèn)題:
-
子類(lèi)不能減少超類(lèi)的接口。你必須實(shí)現(xiàn)父類(lèi)中所有的抽象方法,即使它們沒(méi)什么用。
-
在重寫(xiě)方法時(shí),你需要確保新行為與其基類(lèi)中的版本兼容。這一點(diǎn)很重要,因?yàn)樽宇?lèi)的所有對(duì)象都可能被傳遞給以超類(lèi)對(duì)象為參數(shù)的任何代碼,相信你不會(huì)希望這些代碼崩潰的。
-
繼承打破了超類(lèi)的封裝,因?yàn)樽宇?lèi)擁有訪問(wèn)父類(lèi)內(nèi)部詳細(xì)內(nèi)容的權(quán)限。此外還可能會(huì)有相反的情況出現(xiàn),那就是程序員為了進(jìn)一步擴(kuò)展的方便而讓超類(lèi)知曉子類(lèi)的內(nèi)部詳細(xì)內(nèi)容。
-
子類(lèi)與超類(lèi)緊密耦合。超類(lèi)中的任何修改都可能會(huì)破壞子類(lèi)的功能。
-
通過(guò)繼承復(fù)用代碼可能導(dǎo)致平行繼承體系的產(chǎn)生。繼承通常僅發(fā)生在一個(gè)維度中。只要出現(xiàn)了兩個(gè)以上的維度,你就必須創(chuàng)建數(shù)量巨大的類(lèi)組合,從而使類(lèi)層次結(jié)構(gòu)膨脹到不可思議的程度。
?
組合是代替繼承的一種方法。繼承代表類(lèi)之間的“是”關(guān)系(汽車(chē)是交通工具),而組合則代表“有”關(guān)系(汽車(chē)有一個(gè)引擎)。
?
DRY 原則
?
DRY-Don't Repeat Yourself(不要重復(fù)代碼)
降低可管理單元的復(fù)雜度的基本策略是將系統(tǒng)分成多個(gè)部分。
?
理解這一原理是如此重要,它通常以首字母縮寫(xiě)詞DRY來(lái)指代,并出現(xiàn)在Andy Hunt和Dave Thomas的書(shū)《實(shí)用程序員》中,但是這個(gè)概念本身已經(jīng)有很長(zhǎng)時(shí)間了。它指的是軟件的最小部分。
?
當(dāng)您構(gòu)建一個(gè)大型軟件項(xiàng)目時(shí),通常會(huì)因整體復(fù)雜性而感到不知所措。人類(lèi)不善于管理復(fù)雜性;他們擅長(zhǎng)為特定范圍的問(wèn)題找到有創(chuàng)意的解決方案。降低可管理單元的復(fù)雜性的基本策略是將系統(tǒng)分成更方便的部分。首先,您可能希望將系統(tǒng)分為多個(gè)組件,其中每個(gè)組件代表其自己的子系統(tǒng),其中包含完成特定功能所需的一切。
?
KISS 原則
?
?
KISS是使它保持簡(jiǎn)單,愚蠢的首字母縮寫(xiě),是美國(guó)海軍在1960年提出的設(shè)計(jì)原則。KISS原則指出,大多數(shù)系統(tǒng)如果保持簡(jiǎn)單而不是變得復(fù)雜,則效果最佳。因此,簡(jiǎn)單性應(yīng)該是設(shè)計(jì)的主要目標(biāo),并且應(yīng)該避免不必要的復(fù)雜性。
?
?
SOLID 原則
?
SOLID 原則是在羅伯特·馬丁的著作《敏捷軟件開(kāi)發(fā):原則、模式與實(shí)踐》中首次提出的,SOLID 是讓軟件設(shè)計(jì)更易于理解、更加靈活和更易于維護(hù)的五個(gè)原則的簡(jiǎn)稱(chēng)。
?
?
盡量讓每個(gè)類(lèi)或者函數(shù)只負(fù)責(zé)軟件中的一個(gè)功能,這條原則的主要目的是減少?gòu)?fù)雜度,你不需要費(fèi)盡心機(jī)地去構(gòu)思如何僅用200 行代碼來(lái)實(shí)現(xiàn)復(fù)雜設(shè)計(jì),實(shí)際上完全可以使用十幾個(gè)清晰的方法,這里核心是:?通過(guò)實(shí)現(xiàn)最基本"原子函數(shù)", 其他復(fù)雜功能都可以通過(guò)這些原子函數(shù)構(gòu)建,每一層的函數(shù)語(yǔ)義都是單一的,通過(guò)層層封裝,最終構(gòu)建一個(gè)龐大可控的系統(tǒng)。
?
?
本原則的主要理念是在實(shí)現(xiàn)新功能時(shí)能保持已有代碼不變,為什么呢,主要是修改存量代碼,很可能會(huì)影響軟件穩(wěn)定性,很多線上代碼跑了好多年了,經(jīng)歷很多輪迭代,各種補(bǔ)丁,如果考慮不全面,很容易帶來(lái)風(fēng)險(xiǎn),下圖比較形象說(shuō)明:
?
?
?
替換原則是用于預(yù)測(cè)子類(lèi)是否與代碼兼容,以及是否能與其超類(lèi)對(duì)象協(xié)作的一組檢查。這一概念在開(kāi)發(fā)程序庫(kù)和框架時(shí)非常重要, 因?yàn)槠渲械念?lèi)將會(huì)在他人的代碼中使用——你是無(wú)法直接訪問(wèn)和修改這些代碼的。里氏替換原則的重點(diǎn)在不影響原功能。
?
?
根據(jù)接口隔離原則,你必須將“臃腫”的方法拆分為多個(gè)顆粒度更小的具體方法。客戶(hù)端必須僅實(shí)現(xiàn)其實(shí)際需要的方法。否則,對(duì)于“臃腫”接口的修改可能會(huì)導(dǎo)致程序出錯(cuò),即使客戶(hù)端根本沒(méi)有使用修改后的方法。
?
?
通常在設(shè)計(jì)軟件時(shí),你可以辨別出不同層次的類(lèi)。
? 低層次的類(lèi)實(shí)現(xiàn)基礎(chǔ)操作(例如磁盤(pán)操作、傳輸網(wǎng)絡(luò)數(shù)據(jù)和連接數(shù)據(jù)庫(kù)等)。
? 高層次類(lèi)包含復(fù)雜業(yè)務(wù)邏輯以指導(dǎo)低層次類(lèi)執(zhí)行特定操作。
?
經(jīng)典設(shè)計(jì)模式
?
這里列舉了22種設(shè)計(jì)模式,大致分為三類(lèi):創(chuàng)建型模式,結(jié)構(gòu)型模式,行為模式;
?
創(chuàng)建型模式提供創(chuàng)建對(duì)象的機(jī)制,增加已有代碼的靈活性和可復(fù)用性
?
?
結(jié)構(gòu)型模式介紹如何將對(duì)象和類(lèi)組裝成較大的結(jié)構(gòu),并同時(shí)保持結(jié)構(gòu)的靈活和高效:
?
?
行為模式負(fù)責(zé)對(duì)象間的高效溝通和職責(zé)委派:
?
?
推薦一個(gè)經(jīng)典學(xué)習(xí)網(wǎng)站:
https://refactoring.guru
?
上面每種模式配有形象圖,比如工廠方法模式:
?
?
還提供對(duì)應(yīng)的設(shè)計(jì)類(lèi)圖:
?
?
也提供了對(duì)應(yīng)代碼示例:
?
?
支持9種語(yǔ)言的實(shí)現(xiàn):
? ? 代碼在:https://github.com/RefactoringGuru
?
推薦給大家,拿走別謝;
?
?
更多請(qǐng)參考:
《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
? https://refactoring.guru
?
?
Linux經(jīng)典設(shè)計(jì)模式
?
?內(nèi)核面向?qū)ο笤O(shè)計(jì)模式
?
Linux雖然是面向過(guò)程的c語(yǔ)言寫(xiě)成的,但是卻可以表達(dá)面向?qū)ο蟮乃枷?#xff0c;Linux內(nèi)核大量使用面向?qū)ο蟮木幋a風(fēng)格,我們可以從中至少學(xué)習(xí)到兩點(diǎn):
-
說(shuō)明在大型軟件開(kāi)發(fā)中,OOP編程思想很重要,和具體語(yǔ)言無(wú)關(guān);
-
同時(shí)展示了怎么用c語(yǔ)言實(shí)現(xiàn)OOP編程,值得廣大C語(yǔ)言開(kāi)發(fā)者學(xué)習(xí)。
我們用例子來(lái)說(shuō)明。
?
封裝
以?xún)?nèi)核proto定義為例:
?
struct proto 定義傳輸層接口方法和相應(yīng)成員數(shù)據(jù),類(lèi)似C++的class定義;可以根據(jù)這個(gè)class生產(chǎn)很多實(shí)例,比如TCP實(shí)例,可以通過(guò)統(tǒng)一接口訪問(wèn)TCP實(shí)例的方法和數(shù)據(jù)。
?
繼承
以?xún)?nèi)核套接字體系為例:
?
?
基于此繼承體系,對(duì)于一些接受 struct sock* 形參的接口,就可以直接把上述的子類(lèi)套接字實(shí)例 struct udp_sock* sk作為實(shí)參傳進(jìn)去(當(dāng)然,這里需要指針強(qiáng)轉(zhuǎn)一次(struct sock*)sk)。這里就是OOP中“is a"的public繼承關(guān)系,子類(lèi)對(duì)象可以直接作為父類(lèi)對(duì)象使用,并且這種實(shí)現(xiàn)只支持單繼承。
?
多態(tài)
?
用C實(shí)現(xiàn)多態(tài)需要自己維護(hù)繼承關(guān)系中的虛函數(shù)體系,C++有編譯器自動(dòng)生成、維護(hù)vtbl與vptr。Linux內(nèi)核的實(shí)現(xiàn)中,將系列函數(shù)指針?lè)湃虢Y(jié)構(gòu)體,即視其為“虛函數(shù)”,亦或是專(zhuān)門(mén)定義一個(gè)xxx_ops結(jié)構(gòu),里面放上一堆函數(shù)指針,作為“虛函數(shù)表”。仍以套接字體系為例,在基類(lèi) sock 中,有協(xié)議結(jié)構(gòu)體指針 struct proto *skc_prot; 這個(gè)proto即可大體上視為一個(gè)虛函數(shù)表vtbl,內(nèi)有具體協(xié)議的函數(shù)指針,而這個(gè)skc_prot指針,即可視為虛指針vptr。
?
?
在套接字創(chuàng)建時(shí),根據(jù)參數(shù)中的協(xié)議族、協(xié)議類(lèi)型、協(xié)議號(hào)信息,調(diào)用協(xié)議族的create函數(shù)執(zhí)行創(chuàng)建,綁定具體協(xié)議proto指針到該vptr上,自此實(shí)現(xiàn)了靜態(tài)類(lèi)型到動(dòng)態(tài)類(lèi)型的綁定。之后,當(dāng)調(diào)用虛函數(shù)時(shí),即可直接通過(guò)這些函數(shù)指針進(jìn)行多態(tài)的調(diào)用 , 比如下面例子socket調(diào)用connect接口:
?
這里第一個(gè)參數(shù)sk即可看做this指針,不同socket對(duì)象,會(huì)訪問(wèn)對(duì)應(yīng)協(xié)議接口,從而實(shí)現(xiàn)多態(tài)訪問(wèn):
?
list 設(shè)計(jì)模式
?
list作為常用數(shù)據(jù)結(jié)構(gòu),寫(xiě)代碼時(shí)候經(jīng)常會(huì)遇到,可以看一下傳統(tǒng)list設(shè)計(jì)和內(nèi)核list設(shè)計(jì)有什么不一樣。
?
一般的雙向鏈表一般是如下的結(jié)構(gòu):
-
有個(gè)單獨(dú)的頭結(jié)點(diǎn)(head)
-
每個(gè)節(jié)點(diǎn)(node)除了包含必要的數(shù)據(jù)之外,還有2個(gè)指針(pre,next)
-
pre指針指向前一個(gè)節(jié)點(diǎn)(node),next指針指向后一個(gè)節(jié)點(diǎn)(node)
-
頭結(jié)點(diǎn)(head)的pre指針指向鏈表的最后一個(gè)節(jié)點(diǎn)
-
最后一個(gè)節(jié)點(diǎn)的next指針指向頭結(jié)點(diǎn)(head)
?
?
傳統(tǒng)list如下圖:
?
傳統(tǒng)的鏈表不同node類(lèi)型,需要重新定義結(jié)構(gòu),不夠通用化,還需要為node實(shí)現(xiàn)脫鏈、入鏈操作等。
?
我們需要抽象出一個(gè)“基類(lèi)”來(lái)實(shí)現(xiàn)鏈表的功能,其他數(shù)據(jù)結(jié)構(gòu)只需要簡(jiǎn)單的繼承這個(gè)鏈表類(lèi)就可以了。
?
內(nèi)核list設(shè)計(jì)如下:
?
-
鏈表不是將用戶(hù)數(shù)據(jù)保存在鏈表節(jié)點(diǎn)中,而是將鏈表節(jié)點(diǎn)保存在用戶(hù)數(shù)據(jù)中
-
鏈表節(jié)點(diǎn)只有2個(gè)指針(prev和next)
-
prev指針指向前一個(gè)節(jié)點(diǎn)的鏈表節(jié)點(diǎn),next指針指向后一個(gè)節(jié)點(diǎn)(node)的鏈表節(jié)點(diǎn)
?
?
如下圖:
?
?
這樣設(shè)計(jì)的好處是鏈表的節(jié)點(diǎn)將獨(dú)立于用戶(hù)數(shù)據(jù)之外,便于把鏈表的操作獨(dú)立出來(lái),和具體數(shù)據(jù)節(jié)點(diǎn)無(wú)關(guān),這里可能有些人會(huì)問(wèn),數(shù)據(jù)節(jié)點(diǎn)怎么訪問(wèn)呢?? 內(nèi)核通過(guò)一個(gè)container_of的宏從鏈表節(jié)點(diǎn)找到數(shù)據(jù)節(jié)點(diǎn)起始地址:
?
?
?
找到數(shù)據(jù)節(jié)點(diǎn)起始地址后,通過(guò)數(shù)據(jù)節(jié)點(diǎn)定義就可以訪問(wèn)數(shù)據(jù)了,內(nèi)核紅黑樹(shù)rbtree也是同樣的設(shè)計(jì)。
?
設(shè)備驅(qū)動(dòng)框架設(shè)計(jì)模式
?
從Linux2.6開(kāi)始Linux加入了一套驅(qū)動(dòng)管理和注冊(cè)機(jī)制—platform平臺(tái)總線驅(qū)動(dòng)模型:
?
?
?
當(dāng)調(diào)用platform_device_register(或platform_driver_register)注冊(cè)platform_device(或platform_driver)時(shí),首先會(huì)將其加入platform總線上,依次匹配platform總線上的platform_driver(或platform_device),然后調(diào)用platform_driver的.probe函數(shù)。其中platform_device存放設(shè)備資源(硬件息息相關(guān)代碼,易變動(dòng)),platform_driver則使用資源(比較穩(wěn)定的代碼),這樣當(dāng)改動(dòng)硬件資源時(shí),我們的上層使用資源的代碼部分幾乎可以不用去改動(dòng)。
?
這里設(shè)計(jì)通過(guò)中間bus層,把強(qiáng)耦合Device和對(duì)應(yīng)Driver進(jìn)行了解耦隔離,定好match,probe等標(biāo)準(zhǔn)通信接口,就可以獨(dú)立開(kāi)發(fā),通過(guò)總線bus進(jìn)行關(guān)聯(lián)通信,有點(diǎn)類(lèi)似中介模式。
?
?
C++ Idioms(設(shè)計(jì)習(xí)語(yǔ))
?
由于篇幅優(yōu)先,這里列舉一些非常重要且非常實(shí)用的C++專(zhuān)有的設(shè)計(jì)模式。
?
RAII-Resource Acquisition Is Initialization
?
‘資源獲取即初始化‘(簡(jiǎn)稱(chēng) RAII)是C++防止內(nèi)存泄露一個(gè)很好解決方案,它結(jié)合構(gòu)造函數(shù)和析構(gòu)函數(shù),把資源生命周期和對(duì)象生命周期綁定起來(lái),在構(gòu)造函數(shù)中獲取資源(這些錯(cuò)誤會(huì)引發(fā)異常),然后將其釋放到析構(gòu)函數(shù)中(永不拋出),并且不需要顯式清理,從而防止忘記釋放資源;
?
?
C ++STL庫(kù)很多類(lèi)遵循RAII設(shè)計(jì)原則,比如std :: string,std :: vector,std :: thread等。
Policy-based class?Design
?
基于策略設(shè)計(jì)又名policy-based class design 是一種基于C++計(jì)算機(jī)程序設(shè)計(jì)模式,以策略(Policy)為基礎(chǔ),并結(jié)合C++的模板元編程。就是將原本復(fù)雜的系統(tǒng),拆解成多個(gè)獨(dú)立運(yùn)作的“策略類(lèi)別”,每一組policy class都只負(fù)責(zé)單純?nèi)缧袨榛蚪Y(jié)構(gòu)的某一方面。多重繼承由于繼承自多組 Base Class,故缺乏型別消息,而Templetes基于型別,擁有豐富的型別消息。多重繼承容易擴(kuò)張,而Templetes的特化不容易擴(kuò)張。Policy-Based Class Design 同時(shí)使用了 Template 以及 Multiple Inheritance 兩項(xiàng)技術(shù),結(jié)合兩者的優(yōu)點(diǎn),看下面例子:
?
?
ResourceManager則稱(chēng)為宿主類(lèi)別(host?class),只需要切換不同?Policy?Class(ReadPolicy?or?WritePolicy),就可以得到不同的功能實(shí)體。Policy不一定要被宿主繼承,只需要用委托完成這一工作。但policies必須遵守一個(gè)隱含的constraint,接口必須一樣,故參數(shù)不能有巨大改變,policy?的一個(gè)重要的特征是,宿主類(lèi)別經(jīng)常(并不一定要)使用多重繼承的機(jī)制去使用多個(gè)?policy?classes.??因此在進(jìn)行?policy?拆解時(shí),必須要盡可能達(dá)成正交分解,policy之間最好彼此獨(dú)立運(yùn)作,不相互影響。?
Pimpl - Pointer to implementation
?
Pimpl是一種廣泛使用的削減編譯依賴(lài)項(xiàng)的技術(shù), 看下面例子可能就明白了:
??
?
因?yàn)閃idget的成員變量有std::string,std::vector和Gadget,那么這些類(lèi)型的頭文件在Widget編譯時(shí)必須出現(xiàn),這意味Widget的用戶(hù)必須包含“gadget.h”。這些增加的頭文件會(huì)增加Widget用戶(hù)的編譯時(shí)間,而且這使得用戶(hù)依賴(lài)于這些頭文件,即如果某個(gè)頭文件的內(nèi)容被改變了,Widget的用戶(hù)就要重新編譯。標(biāo)準(zhǔn)庫(kù)頭文件不會(huì)經(jīng)常改變,但是“gadget.h”可能會(huì)經(jīng)常修改。所以需要Pimp技術(shù)來(lái)消除這種變化影響--隔離變化;
?
?
?
這樣Widget頭文件里面就不需要包含“gadget.h”文件了,再CPP文件中再聲明具體的類(lèi)型:
?
??
?
在這里,我展示了“#include”指令,只為了說(shuō)明所有對(duì)頭文件的依賴(lài)(即std::string,std::vector和Gadget)依然存在。不過(guò)呢,依賴(lài)已經(jīng)從“widget.h”(Widget用戶(hù)可見(jiàn)的和使用的)轉(zhuǎn)移到“widget.cpp”(只有Widget的實(shí)現(xiàn)者才能看見(jiàn)和使用),這樣就把widget頭文件變化影響隔離在內(nèi)部實(shí)現(xiàn)中,對(duì)外接口不變,這里就體會(huì)到這種設(shè)計(jì)模式的好處。
?
CRTP -The curiously recurring template pattern?
?
CRTP (奇異遞歸模板模式)是一種在編譯期實(shí)現(xiàn)多態(tài)方法,是對(duì)運(yùn)行時(shí)多態(tài)一種優(yōu)化,多態(tài)是個(gè)很好的特性,但是動(dòng)態(tài)綁定比較慢,因?yàn)橐樘摵瘮?shù)表。而使用 CRTP,完全消除了動(dòng)態(tài)綁定,降低了繼承帶來(lái)的虛函數(shù)表查詢(xún)開(kāi)銷(xiāo)。
CRTP包含:
-
從模板類(lèi)繼承,
-
使用派生類(lèi)本身作為基類(lèi)的模板參數(shù)。
?
?
這樣做的目的是在基類(lèi)中使用派生類(lèi)。從基礎(chǔ)對(duì)象的角度來(lái)看,派生對(duì)象本身就是對(duì)象,但是是向下轉(zhuǎn)換的對(duì)象。因此,基類(lèi)可以通過(guò)將static_cast自身放入派生類(lèi)來(lái)訪問(wèn)派生類(lèi).
?
總結(jié)
?
為什么要掌握設(shè)計(jì)模式,軟件危機(jī)帶來(lái)剛性要求,設(shè)計(jì)模式提倡的高內(nèi)聚,低耦合,代碼復(fù)用,可擴(kuò)展性等思想,可以給我們軟件設(shè)計(jì)帶來(lái)一些思考,有了思考,就會(huì)產(chǎn)生一些積極變化;
?
理解設(shè)計(jì)模式前提,是要理解背后的設(shè)計(jì)原則,這是整個(gè)設(shè)計(jì)模式的精華;
?
經(jīng)典的設(shè)計(jì)模式包含22種設(shè)計(jì)模式(沒(méi)有解釋器模式,日常開(kāi)發(fā)中,很少使用),大致分為三類(lèi):創(chuàng)建型模式,結(jié)構(gòu)型模式,行為模式;
?
Linux系統(tǒng)里面包含大量設(shè)計(jì)模式思想,面向?qū)ο笤O(shè)計(jì),List/Rbtree抽象設(shè)計(jì),驅(qū)動(dòng)框架bus總線解耦設(shè)計(jì),都值得我們學(xué)習(xí);
?
每種編程語(yǔ)言都會(huì)有一些獨(dú)特特殊習(xí)慣用法,Java的MVC,Golang的對(duì)象池模式(Object Pool)等,文中列舉的C++一些常見(jiàn)的慣用法RAII,Policy-based Design ,Pimpl,CRTP等,對(duì)C++開(kāi)發(fā)來(lái)說(shuō),了解和掌握他們,對(duì)于特定場(chǎng)景問(wèn)題多了一些好的解決方案;
?
設(shè)計(jì)模式是銀彈嗎?不是,就像軟件工程也不是銀彈一樣,這些都只是工具,關(guān)鍵還是看是否真正理解其背后反射出的設(shè)計(jì)精髓,我們需要多一些批判性的思考,沒(méi)有絕對(duì)好壞,軟件設(shè)計(jì)的最終方案很多時(shí)候都是權(quán)衡(trade-off)結(jié)果,但我們的長(zhǎng)期目標(biāo)始終沒(méi)有變化。
?
關(guān)注公眾號(hào),回復(fù)"設(shè)計(jì)模式",打包送你設(shè)計(jì)模式經(jīng)典pdf資料和各種語(yǔ)言源碼。
?
大家好,我是Alex,希望你我都是一個(gè)勤勉的人,依靠自己的力量和毅力,從一堆紛繁復(fù)雜尋覓到了真正的知識(shí),寫(xiě)文章確實(shí)比較累,如果可以幫助到你,那也是值得的,希望大家多多點(diǎn)贊,在看,轉(zhuǎn)發(fā),你的舉手之勞都是我堅(jiān)持寫(xiě)作的動(dòng)力,萬(wàn)分感謝;
本人工作多年,面試經(jīng)驗(yàn)豐富(每年上百場(chǎng)面試),負(fù)責(zé)騰訊云網(wǎng)絡(luò)核心架構(gòu)設(shè)計(jì)和開(kāi)發(fā),對(duì)大規(guī)模,大流量,高并發(fā),高可用,極致高性能系統(tǒng)有一定經(jīng)驗(yàn),想要更多技術(shù)討論,面試跳槽,技術(shù)咨詢(xún),職場(chǎng)經(jīng)驗(yàn)等可以關(guān)注公眾號(hào)和關(guān)注我,還可以進(jìn)群和和一群技術(shù)愛(ài)好者討論技術(shù),聊聊天;
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝總結(jié)
以上是生活随笔為你收集整理的经典永驻,重温设计模式 |硬核!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 经典永不过时!重温设计模式
- 下一篇: 高薪进大厂 | 面试指南