定義:高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象罕拂;抽象不應(yīng)該依賴細(xì)節(jié);細(xì)節(jié)應(yīng)該依賴抽象全陨。
問題由來:類A直接依賴類B爆班,假如要將類A改為依賴類C,則必須通過修改類A的代碼來達(dá)成辱姨。這種場景下柿菩,類A一般是高層模塊,負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯雨涛;類B和類C是低層模塊枢舶,負(fù)責(zé)基本的原子操作;假如修改類A替久,會給程序帶來不必要的風(fēng)險凉泄。
解決方案:將類A修改為依賴接口I,類B和類C各自實現(xiàn)接口I蚯根,類A通過接口I間接與類B或者類C發(fā)生聯(lián)系后众,則會大大降低修改類A的幾率。
依賴倒置原則基于這樣一個事實:相對于細(xì)節(jié)的多變性颅拦,抽象的東西要穩(wěn)定的多蒂誉。以抽象為基礎(chǔ)搭建起來的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來的架構(gòu)要穩(wěn)定的多。在java中距帅,抽象指的是接口或者抽象類右锨,細(xì)節(jié)就是具體的實現(xiàn)類,使用接口或者抽象類的目的是制定好規(guī)范和契約碌秸,而不去涉及任何具體的操作陡蝇,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實現(xiàn)類去完成痊臭。
依賴倒置原則的核心思想是面向接口編程,我們依舊用一個例子來說明面向接口編程比相對于面向?qū)崿F(xiàn)編程好在什么地方登夫。場景是這樣的广匙,母親給孩子講故事,只要給她一本書恼策,她就可以照著書給孩子講故事了鸦致。代碼如下:
class Book{
public String getContent(){
return "很久很久以前有一個阿拉伯的故事……";
}
}
class Mother{
public void narrate(Book book){
System.out.println("媽媽開始講故事");
System.out.println(book.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
}
}
運(yùn)行結(jié)果:
媽媽開始講故事
很久很久以前有一個阿拉伯的故事……
運(yùn)行良好,假如有一天涣楷,需求變成這樣:不是給書而是給一份報紙分唾,讓這位母親講一下報紙上的故事,報紙的代碼如下:
class Newspaper{
public String getContent(){
return "林書豪38+7領(lǐng)導(dǎo)尼克斯擊敗湖人……";
}
}
這位母親卻辦不到狮斗,因為她居然不會讀報紙上的故事绽乔,這太荒唐了,只是將書換成報紙碳褒,居然必須要修改Mother才能讀折砸。假如以后需求換成雜志呢?換成網(wǎng)頁呢沙峻?還要不斷地修改Mother睦授,這顯然不是好的設(shè)計。原因就是Mother與Book之間的耦合性太高了摔寨,必須降低他們之間的耦合度才行去枷。
我們引入一個抽象的接口IReader。讀物是复,只要是帶字的都屬于讀物:
interface IReader{
public String getContent();
}
Mother類與接口IReader發(fā)生依賴關(guān)系删顶,而Book和Newspaper都屬于讀物的范疇,他們各自都去實現(xiàn)IReader接口淑廊,這樣就符合依賴倒置原則了翼闹,代碼修改為:
class Newspaper implements IReader {
public String getContent(){
return "林書豪17+9助尼克斯擊敗老鷹……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一個阿拉伯的故事……";
}
}
class Mother{
public void narrate(IReader reader){
System.out.println("媽媽開始講故事");
System.out.println(reader.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}
運(yùn)行結(jié)果:
媽媽開始講故事
很久很久以前有一個阿拉伯的故事……
媽媽開始講故事
林書豪17+9助尼克斯擊敗老鷹……
這樣修改后,無論以后怎樣擴(kuò)展Client類蒋纬,都不需要再修改Mother類了。這只是一個簡單的例子坚弱,實際情況中蜀备,代表高層模塊的Mother類將負(fù)責(zé)完成主要的業(yè)務(wù)邏輯,一旦需要對它進(jìn)行修改荒叶,引入錯誤的風(fēng)險極大碾阁。所以遵循依賴倒置原則可以降低類之間的耦合性,提高系統(tǒng)的穩(wěn)定性些楣,降低修改程序造成的風(fēng)險脂凶。
采用依賴倒置原則給多人并行開發(fā)帶來了極大的便利宪睹,比如上例中,原本Mother類與Book類直接耦合時蚕钦,Mother類必須等Book類編碼完成后才可以進(jìn)行編碼亭病,因為Mother類依賴于Book類。修改后的程序則可以同時開工嘶居,互不影響罪帖,因為Mother與Book類一點(diǎn)關(guān)系也沒有。參與協(xié)作開發(fā)的人越多邮屁、項目越龐大整袁,采用依賴導(dǎo)致原則的意義就越重大。現(xiàn)在很流行的TDD開發(fā)模式就是依賴倒置原則最成功的應(yīng)用佑吝。
傳遞依賴關(guān)系有三種方式坐昙,以上的例子中使用的方法是接口傳遞,另外還有兩種傳遞方式:構(gòu)造方法傳遞和setter方法傳遞芋忿,相信用過Spring框架的炸客,對依賴的傳遞方式一定不會陌生。
在實際編程中盗飒,我們一般需要做到如下3點(diǎn):
- 低層模塊盡量都要有抽象類或接口嚷量,或者兩者都有。
- 變量的聲明類型盡量是抽象類或接口逆趣。
- 使用繼承時遵循里氏替換原則蝶溶。
依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程宣渗,也就理解了依賴倒置抖所。