架構(gòu)中的設(shè)計(jì)原則
在使用面向?qū)ο蟮乃枷脒M(jìn)行系統(tǒng)設(shè)計(jì)時(shí),前人共總結(jié)出了7條原則:?jiǎn)我宦氊?zé)原則、開(kāi)閉原則、里氏替換原則、依賴注入原則、接口分離原則、迪米特原則和優(yōu)先使用組合而不是繼承原則。
1. 單一原則
核心思想:系統(tǒng)中的每一個(gè)對(duì)象都應(yīng)該只有一個(gè)單獨(dú)的職責(zé),而所有的對(duì)象所關(guān)注的就是自身職責(zé)的完成。
Single Responsibility Principle
每個(gè)類應(yīng)該只有一個(gè)職責(zé),對(duì)外只能提供一種功能,而引起類變化的原因應(yīng)該只有一個(gè)。在設(shè)計(jì)模式中,所有的設(shè)計(jì)模式都遵循這一原則。
通常,一個(gè)類的“職責(zé)”越多,導(dǎo)致其變化的因素也就越多。因?yàn)槊總€(gè)職責(zé)都可能是一個(gè)變化的軸線。一般,我們?cè)谠O(shè)計(jì)一個(gè)類的時(shí)候,會(huì)把與該類有關(guān)的操作都組合到這個(gè)類中,這樣設(shè)計(jì)的后果就有可能將多個(gè)職責(zé)“耦合”到了一塊,當(dāng)這個(gè)類的某個(gè)職責(zé)發(fā)生變化時(shí),很難避免其他的部分不受影響。
解決這種問(wèn)題的方法就是“分藕”,將不同的職責(zé)分別進(jìn)行封裝,不要將其組合在一個(gè)類中。比如使用多個(gè)接口定義業(yè)務(wù)操作,每個(gè)接口所定義的業(yè)務(wù)都是單一的。
5點(diǎn)注意:
- 一個(gè)合理的類,應(yīng)該僅有一個(gè)引起它變化的原因,即單一原則;
- 在沒(méi)有變化征兆的情況下使用
SRP或其他原則是不明智的; - 在需求實(shí)際發(fā)生變化時(shí)就應(yīng)該應(yīng)用
SRP等原則來(lái)重構(gòu)代碼; - 使用測(cè)試驅(qū)動(dòng)開(kāi)發(fā)會(huì)迫使我們?cè)谠O(shè)計(jì)出劣質(zhì)代碼之前就分理出不合理代碼;
- 如果測(cè)試不能迫使職責(zé)分離,僵化性和脆弱性的腐朽味會(huì)變得很濃烈,那就應(yīng)該使用
Facade(外觀)或Proxy(代理)模式對(duì)代碼重構(gòu);
2. 里氏替換原則
核心思想:在任何父類出現(xiàn)的地方都可以用它的子類來(lái)替代
Liskov Subsitution Principle
同一個(gè)集成體系中的對(duì)象應(yīng)該有共同的行為特征。里氏替換原則關(guān)注的是怎樣良好地使用繼承,也就是說(shuō)不要亂用繼承,它是繼承復(fù)用的基石。只要父類出現(xiàn)的地方,子類就能出現(xiàn),而且替換為子類不會(huì)產(chǎn)生任何錯(cuò)誤或異常。反過(guò)來(lái)可能就出現(xiàn)問(wèn)題了。
4層含義:
- 子類必須完全實(shí)現(xiàn)父類的方法;
- 子類可以用擁有自己的特性;
- 覆蓋或者實(shí)現(xiàn)父類的方法時(shí)輸入?yún)?shù)可以被放大;(結(jié)合重載考慮父類方法參數(shù)是HashMap而子類方法參數(shù)是Map)
- 覆蓋或者實(shí)現(xiàn)父類的方法時(shí)輸出結(jié)果可以被縮小;
父類能出現(xiàn)的地方子類就可以出現(xiàn),而且替換為子類不會(huì)產(chǎn)生任務(wù)錯(cuò)誤或者異常,使用者也無(wú)需知道是父類還是子類,但是反過(guò)來(lái)就不行了。
3. 依賴注入原則
核心思想:要依賴抽象,不要依賴于具體的實(shí)現(xiàn),
Dependence Inversion Principle(也可以翻譯為依賴反轉(zhuǎn)原則)
在應(yīng)用程序中,所有的類如果使用或依賴于其他的類,則都應(yīng)該依賴于這些類的抽象類,而不是這些類的具體實(shí)現(xiàn)類。抽象層次應(yīng)該不依賴于具體的實(shí)現(xiàn)細(xì)節(jié),這樣才能保證系統(tǒng)的可復(fù)用性和可維護(hù)性。就要要求開(kāi)發(fā)人員面向接口編程而非針對(duì)實(shí)現(xiàn)編程。
3點(diǎn)說(shuō)明:
- 高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴于抽象(抽象類或接口);
- 抽象(抽象類或接口)不應(yīng)該依賴于細(xì)節(jié)(具體實(shí)現(xiàn)類);
- 細(xì)節(jié)(具體實(shí)現(xiàn)類)應(yīng)該依賴抽象;
本質(zhì)是通過(guò)抽象(抽象類或接口)使各個(gè)類或模塊的實(shí)例彼此獨(dú)立,互不影響,實(shí)現(xiàn)模塊間的松耦合。這個(gè)原則也是6個(gè)原則中最難以實(shí)現(xiàn)的,如果沒(méi)有實(shí)現(xiàn)這個(gè)原則,那么意味著開(kāi)閉原則(對(duì)擴(kuò)展開(kāi)發(fā),對(duì)修改關(guān)閉)也無(wú)法實(shí)現(xiàn)。
3種實(shí)現(xiàn)方式:
- 通過(guò)構(gòu)造函數(shù)傳遞依賴對(duì)象; 構(gòu)造函數(shù)中需要傳遞的參數(shù)是抽象類或接口的方式實(shí)現(xiàn);
- 通過(guò)
setter方法傳遞依賴對(duì)象;我們?cè)O(shè)置的set方法中,參數(shù)為抽象類或接口,來(lái)實(shí)現(xiàn)傳遞依賴對(duì)象; - 接口聲明實(shí)現(xiàn)依賴對(duì)象
public interface IFood{
public void eat();
}
public class Noodle implements IFood{
@Override
public void eat(){
System.out.println("吃面條~~");
}
}
public class Rice implements IFood{
@Override
public void eat(){
System.out.println("吃米飯~~");
}
}
...
public interface Man{
public void cook(IFood food);
}
public class Cooker implements Man{
@Override
public void cook(IFood food){
food.eat();
}
}
public class App{
public static void main(String [] args){
Cooker cooker = new Cooker();
IFood noodel = new Noodle();
//cooker.cook(noodel);
IFood rice = new Rice();
//cooker.cook(rice);
...
}
}
這樣各個(gè)類或模塊的實(shí)現(xiàn)彼此獨(dú)立,互補(bǔ)影響,實(shí)現(xiàn)了模塊間的松耦合。
4. 接口分離原則
核心思想:不應(yīng)該強(qiáng)迫客戶程序依賴他們不需要使用的方法,
Interface Segregation Principle,一個(gè)不需要提供太多的行為,一個(gè)接口應(yīng)該只提供一種對(duì)外的功能,不應(yīng)該把所有的操作都封裝在一個(gè)接口中。
接口分離原則要求的是在一個(gè)模塊中應(yīng)該只依賴它需要的接口,以保證接口小純潔,而且要保證接口應(yīng)該盡量小。
接口分離原則與單一職責(zé)原則有點(diǎn)類似,不過(guò)不同在于:?jiǎn)我宦氊?zé)原則要求的是類和接口職責(zé)單一,注重是職責(zé),業(yè)務(wù)邏輯的劃分。而接口分離原則要求的是接口的方法盡量少,針對(duì)單一模塊盡量有用。
3點(diǎn)規(guī)范:
- 接口盡量?。簽榱吮WC一個(gè)接口只服務(wù)于一個(gè)子模塊或者業(yè)務(wù)邏輯;
- 接口高內(nèi)聚:接口高內(nèi)聚是對(duì)內(nèi)高度依賴,對(duì)外盡可能隔離。即一個(gè)接口內(nèi)部聲明的方法相互之間都與某一個(gè)子模塊相關(guān),且是這個(gè)模塊必需的;
- 接口設(shè)計(jì)是有限度的,65535;
5. 迪米特原則
核心思想:一個(gè)對(duì)象應(yīng)當(dāng)對(duì)其他對(duì)象盡可能少地了解,降低各個(gè)對(duì)象之間的耦合,提高系統(tǒng)的可維護(hù)性。在模塊間,應(yīng)該只通過(guò)接口來(lái)通信,而不理會(huì)模塊的內(nèi)部工作原理,促進(jìn)軟件的復(fù)用。
7點(diǎn)注意事項(xiàng):
- 在類的劃分上,應(yīng)該創(chuàng)建有弱耦合的類;
- 在類的結(jié)構(gòu)設(shè)計(jì)上,每一個(gè)都應(yīng)當(dāng)盡量降低成員的訪問(wèn)權(quán)限;
- 在類的設(shè)計(jì)上,只要有可能,一個(gè)類應(yīng)當(dāng)設(shè)計(jì)成不可變類;
- 在對(duì)其他類的引用上,一個(gè)對(duì)象對(duì)其他對(duì)象的引用應(yīng)當(dāng)降到最低;
- 盡量降低類的訪問(wèn)權(quán)限;
- 謹(jǐn)慎使用序列化功能;
- 不要暴露類成員,而應(yīng)該提供相應(yīng)的訪問(wèn)器(屬性);
6. 開(kāi)閉原則
核心思想:一個(gè)對(duì)象對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉
Open for Extension,Closed for Modification。
意思對(duì)類的改動(dòng)通過(guò)增加代碼進(jìn)行的,而不是改動(dòng)現(xiàn)有的代碼。開(kāi)發(fā)人員一旦寫(xiě)出了可行的代碼,就不應(yīng)該去改變它,而是要保證它能一直運(yùn)行下去。這就要借助抽象和多態(tài),把可能變化的內(nèi)容抽象出來(lái),從而抽象出來(lái)的部分是相對(duì)穩(wěn)定的,而具體的實(shí)現(xiàn)層是可以改變和擴(kuò)展的。
開(kāi)閉原則是前5種原則的一個(gè)抽象總結(jié),前5種是開(kāi)閉原則的一些具體體現(xiàn)。所以如果使用開(kāi)閉原則,其實(shí)有點(diǎn)虛,因?yàn)樗鼪](méi)有一個(gè)固定的模式,但是最終保證的是提高程序的復(fù)用性、可維護(hù)性等要求。
寫(xiě)在最后:
不過(guò)這些設(shè)計(jì)原則并不是絕對(duì)的,而是根據(jù)項(xiàng)目的實(shí)際需求來(lái)定奪。