SOLID
分別為:
- 單一職責(zé)原則
- 開閉原則
- 里氏替換原則
- 接口隔離原則
- 依賴倒置原則
單一職責(zé)原則
1. 如何理解單一職責(zé)原則(SRP)玫荣?
一個(gè)類只負(fù)責(zé)完成一個(gè)職責(zé)或者功能捅厂。不要設(shè)計(jì)大而全的類资柔,要設(shè)計(jì)粒度小、功能單一的類辙芍。單一職責(zé)原則是為了實(shí)現(xiàn)代碼高內(nèi)聚沸手、低耦合注簿,提高代碼的復(fù)用性跳仿、可讀性、可維護(hù)性妄辩。
2. 如何判斷類的職責(zé)是否足夠單一眼耀?
不同的應(yīng)用場(chǎng)景、不同階段的需求背景干花、不同的業(yè)務(wù)層面楞黄,對(duì)同一個(gè)類的職責(zé)是否單一鬼廓,可能會(huì)有不同的判定結(jié)果。實(shí)際上尤慰,一些側(cè)面的判斷指標(biāo)更具有指導(dǎo)意義和可執(zhí)行性雷蹂,比如萎河,出現(xiàn)下面這些情況就有可能說明這類的設(shè)計(jì)不滿足單一職責(zé)原則:
- 類中的代碼行數(shù)、函數(shù)或者屬性過多玛歌;
- 類依賴的其他類過多支子,或者依賴類的其他類過多达舒;
- 私有方法過多;比較難給類起一個(gè)合適的名字昨登;
- 類中大量的方法都是集中操作類中的某幾個(gè)屬性贯底。
3. 類的職責(zé)是否設(shè)計(jì)得越單一越好?
單一職責(zé)原則通過避免設(shè)計(jì)大而全的類飘哨,避免將不相關(guān)的功能耦合在一起琐凭,來(lái)提高類的內(nèi)聚性统屈。
同時(shí),類職責(zé)單一囤采,類依賴的和被依賴的其他類也會(huì)變少惩淳,減少了代碼的耦合性思犁,以此來(lái)實(shí)現(xiàn)代碼的高內(nèi)聚、低耦合棉磨。
但是学辱,如果拆分得過細(xì)策泣,實(shí)際上會(huì)適得其反,反倒會(huì)降低內(nèi)聚性统抬,也會(huì)影響代碼的可維護(hù)性聪建。
開閉原則
1. 如何理解“對(duì)擴(kuò)展開放茫陆、對(duì)修改關(guān)閉”盅弛?
添加一個(gè)新的功能叔锐,應(yīng)該是通過在已有代碼基礎(chǔ)上擴(kuò)展代碼(新增模塊见秽、類解取、方法返顺、屬性等)遂鹊,而非修改已有代碼(修改模塊、類慧邮、方法误澳、屬性等)的方式來(lái)完成秦躯。
關(guān)于定義踱承,我們有兩點(diǎn)要注意。
- 第一點(diǎn)是毡琉,開閉原則并不是說完全杜絕修改桅滋,而是以最小的修改代碼的代價(jià)來(lái)完成新功能的開發(fā)身辨。
- 第二點(diǎn)是,同樣的代碼改動(dòng)号俐,在粗代碼粒度下吏饿,可能被認(rèn)定為“修改”;在細(xì)代碼粒度下贞远,可能又被認(rèn)定為“擴(kuò)展”蓝仲。
2. 如何做到“對(duì)擴(kuò)展開放官疲、修改關(guān)閉”途凫?
我們要時(shí)刻具備擴(kuò)展意識(shí)、抽象意識(shí)果元、封裝意識(shí)噪漾。
在寫代碼的時(shí)候且蓬,我們要多花點(diǎn)時(shí)間思考一下恶阴,這段代碼未來(lái)可能有哪些需求變更,如何設(shè)計(jì)代碼結(jié)構(gòu)焦匈,事先留好擴(kuò)展點(diǎn)缓熟,以便在未來(lái)需求變更的時(shí)候摔笤,在不改動(dòng)代碼整體結(jié)構(gòu)吕世、做到最小代碼改動(dòng)的情況下,將新的代碼靈活地插入到擴(kuò)展點(diǎn)上况毅。
里氏替換原則
子類對(duì)象能夠替換程序中父類對(duì)象出現(xiàn)的任何地方尔许,并且保證原來(lái)程序的邏輯行為不變及正確性不被破壞母债。
里式替換原則是用來(lái)指導(dǎo)尝抖,繼承關(guān)系中子類該如何設(shè)計(jì)的一個(gè)原則。
理解里式替換原則衙熔,最核心的就是理解“design by contract红氯,按照協(xié)議來(lái)設(shè)計(jì)”這幾個(gè)字咕痛。
父類定義了函數(shù)的“約定”(或者叫協(xié)議)茉贡,那子類可以改變函數(shù)的內(nèi)部實(shí)現(xiàn)邏輯腔丧,但不能改變函數(shù)原有的“約定”。
這里的約定包括:函數(shù)聲明要實(shí)現(xiàn)的功能砾医;對(duì)輸入如蚜、輸出影暴、異常的約定错邦;甚至包括注釋中所羅列的任何特殊說明。
和多態(tài)的區(qū)別
雖然從定義描述和代碼實(shí)現(xiàn)上來(lái)看坤检,多態(tài)和里式替換有點(diǎn)類似兴猩,但它們關(guān)注的角度是不一樣的。
多態(tài)是面向?qū)ο缶幊痰囊淮筇匦栽缧彩敲嫦驅(qū)ο缶幊陶Z(yǔ)言的一種語(yǔ)法倾芝。它是一種代碼實(shí)現(xiàn)的思路讨勤。
而里式替換是一種設(shè)計(jì)原則,是用來(lái)指導(dǎo)繼承關(guān)系中子類該如何設(shè)計(jì)的晨另,子類的設(shè)計(jì)要保證在替換父類的時(shí)候,不改變?cè)谐绦虻倪壿嬕约安黄茐脑谐绦虻恼_性借尿。
接口隔離原則
1. 如何理解“接口隔離原則”刨晴?
理解“接口隔離原則”的重點(diǎn)是理解其中的“接口”二字。
這里有三種不同的理解路翻。
- 如果把“接口”理解為一組接口集合狈癞,可以是某個(gè)微服務(wù)的接口,也可以是某個(gè)類庫(kù)的接口等茂契。如果部分接口只被部分調(diào)用者使用蝶桶,我們就需要將這部分接口隔離出來(lái),單獨(dú)給這部分調(diào)用者使用掉冶,而不強(qiáng)迫其他調(diào)用者也依賴這部分不會(huì)被用到的接口真竖。
- 如果把“接口”理解為單個(gè) API 接口或函數(shù),部分調(diào)用者只需要函數(shù)中的部分功能厌小,那我們就需要把函數(shù)拆分成粒度更細(xì)的多個(gè)函數(shù)恢共,讓調(diào)用者只依賴它需要的那個(gè)細(xì)粒度函數(shù)。
- 如果把“接口”理解為 OOP 中的接口璧亚,也可以理解為面向?qū)ο缶幊陶Z(yǔ)言中的接口語(yǔ)法讨韭。那接口的設(shè)計(jì)要盡量單一,不要讓接口的實(shí)現(xiàn)類和調(diào)用者涨岁,依賴不需要的接口函數(shù)拐袜。
2. 接口隔離原則與單一職責(zé)原則的區(qū)別
單一職責(zé)原則針對(duì)的是模塊、類梢薪、接口的設(shè)計(jì)蹬铺。
接口隔離原則相對(duì)于單一職責(zé)原則,一方面更側(cè)重于接口的設(shè)計(jì)秉撇,另一方面它的思考角度也是不同的甜攀。
接口隔離原則提供了一種判斷接口的職責(zé)是否單一的標(biāo)準(zhǔn):通過調(diào)用者如何使用接口來(lái)間接地判定。如果調(diào)用者只使用部分接口或接口的部分功能琐馆,那接口的設(shè)計(jì)就不夠職責(zé)單一规阀。
依賴反轉(zhuǎn)
控制反轉(zhuǎn)、依賴注入瘦麸、依賴反轉(zhuǎn)谁撼。
1. 控制反轉(zhuǎn)
實(shí)際上,控制反轉(zhuǎn)是一個(gè)比較籠統(tǒng)的設(shè)計(jì)思想滋饲,并不是一種具體的實(shí)現(xiàn)方法厉碟,一般用來(lái)指導(dǎo)框架層面的設(shè)計(jì)喊巍。這里所說的“控制”指的是對(duì)程序執(zhí)行流程的控制,而“反轉(zhuǎn)”指的是在沒有使用框架之前箍鼓,程序員自己控制整個(gè)程序的執(zhí)行崭参。在使用框架之后,整個(gè)程序的執(zhí)行流程通過框架來(lái)控制款咖。流程的控制權(quán)從程序員“反轉(zhuǎn)”給了框架何暮。
2. 依賴注入
依賴注入和控制反轉(zhuǎn)恰恰相反,它是一種具體的編碼技巧铐殃。我們不通過 new 的方式在類內(nèi)部創(chuàng)建依賴類的對(duì)象海洼,而是將依賴的類對(duì)象在外部創(chuàng)建好之后,通過構(gòu)造函數(shù)背稼、函數(shù)參數(shù)等方式傳遞(或注入)給類來(lái)使用贰军。
3. 依賴注入框架
我們通過依賴注入框架提供的擴(kuò)展點(diǎn)玻蝌,簡(jiǎn)單配置一下所有需要的類及其類與類之間依賴關(guān)系蟹肘,就可以實(shí)現(xiàn)由框架來(lái)自動(dòng)創(chuàng)建對(duì)象、管理對(duì)象的生命周期俯树、依賴注入等原本需要程序員來(lái)做的事情帘腹。
4. 依賴反轉(zhuǎn)原則
依賴反轉(zhuǎn)原則也叫作依賴倒置原則。這條原則跟控制反轉(zhuǎn)有點(diǎn)類似许饿,主要用來(lái)指導(dǎo)框架層面的設(shè)計(jì)阳欲。高層模塊不依賴低層模塊,它們共同依賴同一個(gè)抽象陋率。抽象不要依賴具體實(shí)現(xiàn)細(xì)節(jié)球化,具體實(shí)現(xiàn)細(xì)節(jié)依賴抽象。
KISS瓦糟、YAGNI
KISS
Keep It Simple and Stupid.
Keep It Short and Simple.
Keep It Simple and Straightforward.
KISS 原則是保持代碼可讀和可維護(hù)的重要手段筒愚。
KISS 原則中的“簡(jiǎn)單”并不是以代碼行數(shù)來(lái)考量的。代碼行數(shù)越少并不代表代碼越簡(jiǎn)單菩浙,我們還要考慮邏輯復(fù)雜度巢掺、實(shí)現(xiàn)難度、代碼的可讀性等劲蜻。而且陆淀,本身就復(fù)雜的問題,用復(fù)雜的方法解決先嬉,并不違背 KISS 原則轧苫。
除此之外,同樣的代碼疫蔓,在某個(gè)業(yè)務(wù)場(chǎng)景下滿足 KISS 原則含懊,換一個(gè)應(yīng)用場(chǎng)景可能就不滿足了钾军。對(duì)于如何寫出滿足 KISS 原則的代碼,我還總結(jié)了下面幾條指導(dǎo)原則:
- 不要使用同事可能不懂的技術(shù)來(lái)實(shí)現(xiàn)代碼绢要;
- 不要重復(fù)造輪子吏恭,要善于使用已經(jīng)有的工具類庫(kù);
- 不要過度優(yōu)化重罪。
YAGNI
You Ain’t Gonna Need It.
不要去設(shè)計(jì)當(dāng)前用不到的功能樱哼;不要去編寫當(dāng)前用不到的代碼。
實(shí)際上剿配,這條原則的核心思想就是:不要做過度設(shè)計(jì)搅幅。
DRY
1.DRY 原則
Don't repeat yourself.
不要寫重復(fù)的代碼。
三種代碼重復(fù)的情況:
- 實(shí)現(xiàn)邏輯重復(fù)呼胚、
- 功能語(yǔ)義重復(fù)茄唐、
- 代碼執(zhí)行重復(fù)。
還有文檔注釋重復(fù)蝇更、數(shù)據(jù)結(jié)構(gòu)重復(fù)沪编。兩種
實(shí)現(xiàn)邏輯重復(fù),但功能語(yǔ)義不重復(fù)的代碼年扩,并不違反 DRY 原則蚁廓。
實(shí)現(xiàn)邏輯不重復(fù),但功能語(yǔ)義重復(fù)的代碼厨幻,也算是違反 DRY 原則相嵌。
除此之外,代碼執(zhí)行重復(fù)也算是違反 DRY 原則况脆。
2. 代碼復(fù)用性
減少代碼耦合
對(duì)于高度耦合的代碼饭宾,當(dāng)我們希望復(fù)用其中的一個(gè)功能,想把這個(gè)功能的代碼抽取出來(lái)成為一個(gè)獨(dú)立的模塊格了、類或者函數(shù)的時(shí)候看铆,往往會(huì)發(fā)現(xiàn)牽一發(fā)而動(dòng)全身。移動(dòng)一點(diǎn)代碼笆搓,就要牽連到很多其他相關(guān)的代碼性湿。所以,高度耦合的代碼會(huì)影響到代碼的復(fù)用性满败,我們要盡量減少代碼耦合肤频。
滿足單一職責(zé)原則
我們前面講過,如果職責(zé)不夠單一算墨,模塊宵荒、類設(shè)計(jì)得大而全,那依賴它的代碼或者它依賴的代碼就會(huì)比較多,進(jìn)而增加了代碼的耦合报咳。根據(jù)上一點(diǎn)侠讯,也就會(huì)影響到代碼的復(fù)用性。相反暑刃,越細(xì)粒度的代碼厢漩,代碼的通用性會(huì)越好,越容易被復(fù)用岩臣。
模塊化
這里的“模塊”溜嗜,不單單指一組類構(gòu)成的模塊,還可以理解為單個(gè)類架谎、函數(shù)炸宵。我們要善于將功能獨(dú)立的代碼,封裝成模塊谷扣。獨(dú)立的模塊就像一塊一塊的積木土全,更加容易復(fù)用,可以直接拿來(lái)搭建更加復(fù)雜的系統(tǒng)会涎。
業(yè)務(wù)與非業(yè)務(wù)邏輯分離
越是跟業(yè)務(wù)無(wú)關(guān)的代碼越是容易復(fù)用裹匙,越是針對(duì)特定業(yè)務(wù)的代碼越難復(fù)用。所以在塔,為了復(fù)用跟業(yè)務(wù)無(wú)關(guān)的代碼幻件,我們將業(yè)務(wù)和非業(yè)務(wù)邏輯代碼分離,抽取成一些通用的框架蛔溃、類庫(kù)、組件等篱蝇。
通用代碼下沉
從分層的角度來(lái)看贺待,越底層的代碼越通用、會(huì)被越多的模塊調(diào)用零截,越應(yīng)該設(shè)計(jì)得足夠可復(fù)用麸塞。一般情況下,在代碼分層之后涧衙,為了避免交叉調(diào)用導(dǎo)致調(diào)用關(guān)系混亂哪工,我們只允許上層代碼調(diào)用下層代碼及同層代碼之間的調(diào)用,杜絕下層代碼調(diào)用上層代碼弧哎。所以雁比,通用的代碼我們盡量下沉到更下層。
繼承撤嫩、多態(tài)偎捎、抽象、封裝
在講面向?qū)ο筇匦缘臅r(shí)候,我們講到茴她,利用繼承寻拂,可以將公共的代碼抽取到父類,子類復(fù)用父類的屬性和方法丈牢。利用多態(tài)祭钉,我們可以動(dòng)態(tài)地替換一段代碼的部分邏輯,讓這段代碼可復(fù)用己沛。除此之外朴皆,抽象和封裝,從更加廣義的層面泛粹、而非狹義的面向?qū)ο筇匦缘膶用鎭?lái)理解的話遂铡,越抽象、越不依賴具體的實(shí)現(xiàn)晶姊,越容易復(fù)用扒接。代碼封裝成模塊会放,隱藏可變的細(xì)節(jié)附迷、暴露不變的接口,就越容易復(fù)用境输。
應(yīng)用模板等設(shè)計(jì)模式
一些設(shè)計(jì)模式蒙挑,也能提高代碼的復(fù)用性宗侦。比如,模板模式利用了多態(tài)來(lái)實(shí)現(xiàn)忆蚀,可以靈活地替換其中的部分代碼矾利,整個(gè)流程模板代碼可復(fù)用。
我們?cè)诘谝淮螌懘a的時(shí)候馋袜,如果當(dāng)下沒有復(fù)用的需求男旗,而未來(lái)的復(fù)用需求也不是特別明確,并且開發(fā)可復(fù)用代碼的成本比較高欣鳖,那我們就不需要考慮代碼的復(fù)用性察皇。
在之后開發(fā)新的功能的時(shí)候,發(fā)現(xiàn)可以復(fù)用之前寫的這段代碼泽台,那我們就重構(gòu)這段代碼什荣,讓其變得更加可復(fù)用。相比于代碼的可復(fù)用性怀酷,DRY 原則適用性更強(qiáng)一些稻爬。我們可以不寫可復(fù)用的代碼,但一定不能寫重復(fù)的代碼胰坟。
迪米特法則
最少知道原則因篇。
1. 如何理解“高內(nèi)聚泞辐、松耦合”?
“高內(nèi)聚竞滓、松耦合”是一個(gè)非常重要的設(shè)計(jì)思想咐吼,能夠有效提高代碼的可讀性和可維護(hù)性,縮小功能改動(dòng)導(dǎo)致的代碼改動(dòng)范圍商佑。
“高內(nèi)聚”用來(lái)指導(dǎo)類本身的設(shè)計(jì)锯茄,“松耦合”用來(lái)指導(dǎo)類與類之間依賴關(guān)系的設(shè)計(jì)。
所謂高內(nèi)聚茶没,就是指相近的功能應(yīng)該放到同一個(gè)類中肌幽,不相近的功能不要放到同一類中。相近的功能往往會(huì)被同時(shí)修改抓半,放到同一個(gè)類中喂急,修改會(huì)比較集中。
所謂松耦合指的是笛求,在代碼中廊移,類與類之間的依賴關(guān)系簡(jiǎn)單清晰。即使兩個(gè)類有依賴關(guān)系探入,一個(gè)類的代碼改動(dòng)也不會(huì)或者很少導(dǎo)致依賴類的代碼改動(dòng)狡孔。
2. 如何理解“迪米特法則”?
不該有直接依賴關(guān)系的類之間蜂嗽,不要有依賴苗膝;有依賴關(guān)系的類之間,盡量只依賴必要的接口植旧。
迪米特法則是希望減少類之間的耦合辱揭,讓類越獨(dú)立越好。每個(gè)類都應(yīng)該少了解系統(tǒng)的其他部分隆嗅。一旦發(fā)生變化界阁,需要了解這一變化的類就會(huì)比較少。