簡(jiǎn)述
面向?qū)ο蟮脑O(shè)計(jì)原則有七個(gè),包括:開(kāi)閉原則靠抑、里氏代換原則量九、迪米特原則(最少知道原則)、單一職責(zé)原則颂碧、接口分隔原則荠列、依賴(lài)倒置原則、組合/聚合復(fù)用原則载城。
一般地肌似,可以把這七個(gè)原則分成了以下兩個(gè)部分:
- 設(shè)計(jì)目標(biāo):開(kāi)閉原則、里氏代換原則诉瓦、迪米特原則
- 設(shè)計(jì)方法:?jiǎn)我宦氊?zé)原則川队、接口分隔原則力细、依賴(lài)倒置原則、組合/聚合復(fù)用原則
一固额、開(kāi)閉原則(The Open-Closed Principle 眠蚂,OCP)
概念理解
- 擴(kuò)展開(kāi)放:某模塊的功能是可擴(kuò)展的,則該模塊是擴(kuò)展開(kāi)放的对雪。軟件系統(tǒng)的功能上的可擴(kuò)展性要求模塊是擴(kuò)展開(kāi)放的河狐。
- 修改關(guān)閉:某模塊被其他模塊調(diào)用,如果該模塊的源代碼不允許修改瑟捣,則該模塊修改關(guān)閉的馋艺。軟件系統(tǒng)的功能上的穩(wěn)定性,持續(xù)性要求模塊是修改關(guān)閉的迈套。
系統(tǒng)設(shè)計(jì)需要遵循開(kāi)閉原則的原因
- 穩(wěn)定性捐祠。開(kāi)閉原則要求擴(kuò)展功能不修改原來(lái)的代碼,這可以讓軟件系統(tǒng)在變化中保持穩(wěn)定桑李。
- 擴(kuò)展性踱蛀。開(kāi)閉原則要求對(duì)擴(kuò)展開(kāi)放,通過(guò)擴(kuò)展提供新的或改變?cè)械墓δ芄蟀祝屲浖到y(tǒng)具有靈活的可擴(kuò)展性率拒。
遵循開(kāi)閉原則的系統(tǒng)設(shè)計(jì),可以讓軟件系統(tǒng)可復(fù)用禁荒,并且易于維護(hù)猬膨。
示例
二、里氏替換原則(Liskov Substitution Principle 呛伴,LSP)
概念理解
(1)應(yīng)該滿足下面兩個(gè)條件
- 不應(yīng)該在代碼中出現(xiàn)if/else之類(lèi)對(duì)派生類(lèi)類(lèi)型進(jìn)行判斷的條件勃痴。
- 派生類(lèi)應(yīng)當(dāng)可以替換基類(lèi)并出現(xiàn)在基類(lèi)能夠出現(xiàn)的任何地方,或者說(shuō)如果我們把代碼中使用基類(lèi)的地方用它的派生類(lèi)所代替热康,代碼還能正常工作沛申。
(2)里氏替換原則(LSP)是使代碼符合開(kāi)閉原則的一個(gè)重要保證。
(3)同時(shí)LSP體現(xiàn)了:
類(lèi)的繼承原則:如果一個(gè)派生類(lèi)的對(duì)象可能會(huì)在基類(lèi)出現(xiàn)的地方出現(xiàn)運(yùn)行錯(cuò)誤姐军,則該派生類(lèi)不應(yīng)該從該基類(lèi)繼承铁材,或者說(shuō),應(yīng)該重新設(shè)計(jì)它們之間的關(guān)系奕锌。
動(dòng)作正確性保證:從另一個(gè)側(cè)面上保證了符合LSP設(shè)計(jì)原則的類(lèi)的擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤衫贬。
示例:
里式替換原則的引申意義
子類(lèi)可以擴(kuò)展父類(lèi)的功能,但不能改變父類(lèi)原有的功能歇攻。具體來(lái)說(shuō):
- 子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法固惯,但不能覆蓋父類(lèi)的非抽象方法。
- 子類(lèi)中可以增加自己特有的方法缴守。
- 當(dāng)子類(lèi)的方法重載父類(lèi)的方法時(shí)葬毫,方法的前置條件(即方法的輸入/入?yún)ⅲ┮雀割?lèi)方法的輸入?yún)?shù)更寬松镇辉。
- 當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的方法時(shí)(重載/重寫(xiě)或?qū)崿F(xiàn)抽象方法)的后置條件(即方法的輸出/返回值)要比父類(lèi)更嚴(yán)格或相等。
里式替換原則的優(yōu)點(diǎn)
- 約束繼承泛濫贴捡,是開(kāi)閉原則的一種體現(xiàn)忽肛。
- 加強(qiáng)程序的健壯性,同時(shí)變更時(shí)也可以做到非常好地提高程序的維護(hù)性烂斋、擴(kuò)展性屹逛。降低需求變更時(shí)引入的風(fēng)險(xiǎn)。
如何重構(gòu)違反LSP的設(shè)計(jì)
如果兩個(gè)具體的類(lèi)A汛骂,B之間的關(guān)系違反了LSP 的設(shè)計(jì)罕模,(假設(shè)是從B到A的繼承關(guān)系),那么根據(jù)具體的情況可以在下面的兩種重構(gòu)方案中選擇一種:
- 創(chuàng)建一個(gè)新的抽象類(lèi)C帘瞭,作為兩個(gè)具體類(lèi)的基類(lèi)淑掌,將A,B的共同行為移動(dòng)到C中來(lái)解決問(wèn)題蝶念。
- 從B到A的繼承關(guān)系改為關(guān)聯(lián)關(guān)系抛腕。
三、迪米特原則(最少知道原則)(Law of Demeter 媒殉,LoD)
概念理解
- 一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少地與其他實(shí)體發(fā)生相互作用担敌。
- 每一個(gè)軟件單位對(duì)其他的單位的了解都只有最少的知識(shí),而且局限于那些與本單位密切相關(guān)的軟件單位廷蓉。
優(yōu)缺點(diǎn)
迪米特原則的初衷在于降低類(lèi)之間的耦合全封。由于每個(gè)類(lèi)盡量減少對(duì)其他類(lèi)的依賴(lài),因此苦酱,很容易使得系統(tǒng)的功能模塊功能獨(dú)立售貌,相互之間不存在(或很少有)依賴(lài)關(guān)系给猾。
迪米特原則不希望類(lèi)直接建立直接的接觸疫萤。如果真的有需要建立聯(lián)系,也希望能通過(guò)它的友元類(lèi)來(lái)轉(zhuǎn)達(dá)敢伸。因此扯饶,應(yīng)用迪米特原則有可能造成的一個(gè)后果就是:系統(tǒng)中存在大量的中介類(lèi),這些類(lèi)之所以存在完全是為了傳遞類(lèi)之間的相互調(diào)用關(guān)系池颈,這在一定程度上增加了系統(tǒng)的復(fù)雜度尾序。
四、單一職責(zé)原則
概念理解
只能讓一個(gè)類(lèi)/接口/方法有且僅有一個(gè)職責(zé)躯砰。
為什么要單一職責(zé)
如果一個(gè)類(lèi)具有一個(gè)以上的職責(zé)每币,那么就會(huì)有多個(gè)不同的原因引起該類(lèi)變化,而這種變化將影響到該類(lèi)不同職責(zé)的使用者(不同用戶(hù)):
- 一方面琢歇,如果一個(gè)職責(zé)使用了外部類(lèi)庫(kù)兰怠,則使用另外一個(gè)職責(zé)的用戶(hù)卻也不得不包含這個(gè)未被使用的外部類(lèi)庫(kù)梦鉴。
- 另一方面,某個(gè)用戶(hù)由于某個(gè)原因需要修改其中一個(gè)職責(zé)揭保,另外一個(gè)職責(zé)的用戶(hù)也將受到影響肥橙,他將不得不重新編譯和配置。
這違反了設(shè)計(jì)的開(kāi)閉原則秸侣,也不是我們所期望的存筏。
單一職責(zé)定義
所謂一個(gè)類(lèi)的一個(gè)職責(zé)是指引起該類(lèi)變化的一個(gè)原因。
如果你能想到一個(gè)類(lèi)存在多個(gè)使其改變的原因味榛,那么這個(gè)類(lèi)就存在多個(gè)職責(zé)椭坚。
使用單一職責(zé)原則的理由
單一職責(zé)原則從職責(zé)(改變理由)的側(cè)面上為我們對(duì)類(lèi)(接口)的抽象的顆粒度建立了判斷基準(zhǔn):在為系統(tǒng)設(shè)計(jì)類(lèi)(接口)的時(shí)候應(yīng)該保證它們的單一職責(zé)性。
降低了類(lèi)的復(fù)雜度励负、提高類(lèi)的可讀性藕溅,提高系統(tǒng)的可維護(hù)性、降低變更引起的風(fēng)險(xiǎn)
五继榆、接口分隔原則(Interface Segregation Principle 巾表,ISP)
概念理解
- 接口的設(shè)計(jì)原則:接口的設(shè)計(jì)應(yīng)該遵循最小接口原則,不要把用戶(hù)不使用的方法塞進(jìn)同一個(gè)接口里略吨。如果一個(gè)接口的方法沒(méi)有被使用到集币,則說(shuō)明該接口過(guò)胖,應(yīng)該將其分割成幾個(gè)功能專(zhuān)一的接口翠忠。
- 接口的依賴(lài)(繼承)原則:如果一個(gè)接口a繼承另一個(gè)接口b鞠苟,則接口a相當(dāng)于繼承了接口b的方法,那么繼承了接口b后的接口a也應(yīng)該遵循上述原則:不應(yīng)該包含用戶(hù)不使用的方法秽之。 反之当娱,則說(shuō)明接口a被b給污染了,應(yīng)該重新設(shè)計(jì)它們的關(guān)系考榨。
接口分隔原則的優(yōu)點(diǎn)和適度原則
接口分隔原則從對(duì)接口的使用上為我們對(duì)接口抽象的顆粒度建立了判斷基準(zhǔn):在為系統(tǒng)設(shè)計(jì)接口的時(shí)候跨细,使用多個(gè)專(zhuān)門(mén)的接口代替單一的胖接口。
- 符合高內(nèi)聚低耦合的設(shè)計(jì)思想河质,從而使得類(lèi)具有很好的可讀性冀惭、可擴(kuò)展性和可維護(hù)性。
- 注意適度原則掀鹅,接口分隔要適度散休,避免產(chǎn)生大量的細(xì)小接口。
單一職責(zé)原則和接口分隔原則的區(qū)別
- 單一職責(zé)強(qiáng)調(diào)的是接口乐尊、類(lèi)戚丸、方法的職責(zé)是單一的,強(qiáng)調(diào)職責(zé)扔嵌,方法可以多限府,針對(duì)程序中實(shí)現(xiàn)的細(xì)節(jié)猴鲫;
- 接口分隔原則主要是約束接口,針對(duì)抽象谣殊、整體框架拂共。
六、依賴(lài)倒置原則(Dependency Inversion Principle 姻几,DIP)
概念理解
- 依賴(lài):在程序設(shè)計(jì)中宜狐,如果一個(gè)模塊a使用/調(diào)用了另一個(gè)模塊b,我們稱(chēng)模塊a依賴(lài)模塊b蛇捌。
- 高層模塊與低層模塊:往往在一個(gè)應(yīng)用程序中抚恒,我們有一些低層次的類(lèi),這些類(lèi)實(shí)現(xiàn)了一些基本的或初級(jí)的操作,我們稱(chēng)之為低層模塊;另外有一些高層次的類(lèi)翘悉,這些類(lèi)封裝了某些復(fù)雜的邏輯,并且依賴(lài)于低層次的類(lèi)混萝,這些類(lèi)我們稱(chēng)之為高層模塊。
- 依賴(lài)倒置(Dependency Inversion):
面向?qū)ο蟪绦蛟O(shè)計(jì)相對(duì)于面向過(guò)程(結(jié)構(gòu)化)程序設(shè)計(jì)而言萍恕,依賴(lài)關(guān)系被倒置了逸嘀。因?yàn)閭鹘y(tǒng)的結(jié)構(gòu)化程序設(shè)計(jì)中,高層模塊總是依賴(lài)于低層模塊允粤。
問(wèn)題的提出:
Robert C. Martin氏在原文中給出了“Bad Design”的定義:
- 系統(tǒng)很難改變崭倘,因?yàn)槊總€(gè)改變都會(huì)影響其他很多部分。
- 當(dāng)你對(duì)某地方做一修改类垫,系統(tǒng)的看似無(wú)關(guān)的其他部分都不工作了司光。
- 系統(tǒng)很難被另外一個(gè)應(yīng)用重用,因?yàn)楹茈y將要重用的部分從系統(tǒng)中分離開(kāi)來(lái)悉患。
- 導(dǎo)致“Bad Design”的很大原因是“高層模塊”過(guò)分依賴(lài)“低層模塊”残家。
DIP給出了一個(gè)解決方案:在高層模塊與低層模塊之間,引入一個(gè)抽象接口層购撼。
依賴(lài)倒置原則的優(yōu)點(diǎn)
可以減少類(lèi)間的耦合性跪削、提高系統(tǒng)穩(wěn)定性谴仙,提高代碼可讀性和可維護(hù)性迂求,可降低修改程序所造成的風(fēng)險(xiǎn)。
七晃跺、組合/聚合復(fù)用原則(Composite/Aggregate Reuse Principle 揩局,CARP)
概念理解
即在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象,使之成為新對(duì)象的一部分掀虎,新對(duì)象通過(guò)向這些對(duì)象的委派達(dá)到復(fù)用已有功能的目的凌盯。就是說(shuō)要盡量的使用合成和聚合付枫,而不是繼承關(guān)系達(dá)到復(fù)用的目的。
組合和聚合都是關(guān)聯(lián)的特殊種類(lèi)驰怎。
聚合表示整體和部分的關(guān)系阐滩,表示“擁有”。組合則是一種更強(qiáng)的“擁有”县忌,部分和整體的生命周期一樣掂榔。
組合的新的對(duì)象完全支配其組成部分,包括它們的創(chuàng)建和湮滅等症杏。一個(gè)組合關(guān)系的成分對(duì)象是不能與另一個(gè)組合關(guān)系共享的装获。
在面向?qū)ο笤O(shè)計(jì)中,有兩種基本的辦法可以實(shí)現(xiàn)復(fù)用:第一種是通過(guò)組合/聚合厉颤,第二種就是通過(guò)繼承穴豫。
何時(shí)使用繼承關(guān)系:
1)派生類(lèi)是基類(lèi)的一個(gè)特殊種類(lèi),而不是基類(lèi)的一個(gè)角色逼友,也就是區(qū)分"Has-A"和"Is-A"精肃。只有"Is-A"關(guān)系才符合繼承關(guān)系,"Has-A"關(guān)系應(yīng)當(dāng)用聚合來(lái)描述帜乞。
2)永遠(yuǎn)不會(huì)出現(xiàn)需要將派生類(lèi)換成另外一個(gè)類(lèi)的派生類(lèi)的情況肋杖。如果不能肯定將來(lái)是否會(huì)變成另外一個(gè)派生類(lèi)的話,就不要使用繼承挖函。
3)派生類(lèi)具有擴(kuò)展基類(lèi)的責(zé)任状植,而不是具有置換掉(override)或注銷(xiāo)掉(Nullify)基類(lèi)的責(zé)任。如果一個(gè)派生類(lèi)需要大量的置換掉基類(lèi)的行為怨喘,那么這個(gè)類(lèi)就不應(yīng)該是這個(gè)基類(lèi)的派生類(lèi)津畸。
4)只有在分類(lèi)學(xué)角度上有意義時(shí),才可以使用繼承必怜。
通過(guò)組合/聚合復(fù)用的優(yōu)缺點(diǎn)
(1)優(yōu)點(diǎn):
- 新對(duì)象存取子對(duì)象的唯一方法是通過(guò)子對(duì)象的接口肉拓。
- 這種復(fù)用是黑箱復(fù)用,因?yàn)樽訉?duì)象的內(nèi)部細(xì)節(jié)是新對(duì)象所看不見(jiàn)的梳庆。
- 這種復(fù)用更好地支持封裝性暖途。
- 這種復(fù)用實(shí)現(xiàn)上的相互依賴(lài)性比較小。
- 每一個(gè)新的類(lèi)可以將焦點(diǎn)集中在一個(gè)任務(wù)上膏执。
- 這種復(fù)用可以在運(yùn)行時(shí)間內(nèi)動(dòng)態(tài)進(jìn)行驻售,新對(duì)象可以動(dòng)態(tài)的引用與子對(duì)象類(lèi)型相同的對(duì)象。
- 作為復(fù)用手段可以應(yīng)用到幾乎任何環(huán)境中去更米。
(2)缺點(diǎn): - 就是系統(tǒng)中會(huì)有較多的對(duì)象需要管理欺栗。
通過(guò)繼承來(lái)進(jìn)行復(fù)用的優(yōu)缺點(diǎn)
(1)優(yōu)點(diǎn):
- 新的實(shí)現(xiàn)較為容易,因?yàn)榛?lèi)的大部分功能可以通過(guò)繼承的關(guān)系自動(dòng)進(jìn)入派生類(lèi)。
- 修改和擴(kuò)展繼承而來(lái)的實(shí)現(xiàn)較為容易迟几。
(2)缺點(diǎn): - 繼承復(fù)用破壞封裝性消请,因?yàn)槔^承將基類(lèi)的實(shí)現(xiàn)細(xì)節(jié)暴露給派生類(lèi)。由于基類(lèi)的內(nèi)部細(xì)節(jié)常常是對(duì)于派生類(lèi)透明的类腮,所以這種復(fù)用是透明的復(fù)用臊泰,又稱(chēng)“白箱”復(fù)用。
- 如果基類(lèi)發(fā)生改變蚜枢,那么派生類(lèi)的實(shí)現(xiàn)也不得不發(fā)生改變因宇。
- 從基類(lèi)繼承而來(lái)的實(shí)現(xiàn)是靜態(tài)的,不可能在運(yùn)行時(shí)間內(nèi)發(fā)生改變祟偷,沒(méi)有足夠的靈活性察滑。
歡迎大家關(guān)注我的公眾號(hào)