定義:高層模塊不應(yīng)該依賴(lài)低層模塊,二者都應(yīng)該依賴(lài)其抽象;抽象不應(yīng)該依賴(lài)細(xì)節(jié);細(xì)節(jié)應(yīng)該依賴(lài)抽象。
問(wèn)題由來(lái):類(lèi)A直接依賴(lài)類(lèi)B,假如要將類(lèi)A改為依賴(lài)類(lèi)C,則必須通過(guò)修改類(lèi)A的代碼來(lái)達(dá)成。這種場(chǎng)景下,類(lèi)A一般是高層模塊,負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯;類(lèi)B和類(lèi)C是低層模塊,負(fù)責(zé)基本的原子操作;假如修改類(lèi)A,會(huì)給程序帶來(lái)不必要的風(fēng)險(xiǎn)。
解決方案:將類(lèi)A修改為依賴(lài)接口I,類(lèi)B和類(lèi)C各自實(shí)現(xiàn)接口I,類(lèi)A通過(guò)接口I間接與類(lèi)B或者類(lèi)C發(fā)生聯(lián)系,則會(huì)大大降低修改類(lèi)A的幾率。
依賴(lài)倒置原則基于這樣一個(gè)事實(shí):相對(duì)于細(xì)節(jié)的多變性,抽象的東西要穩(wěn)定的多。以抽象為基礎(chǔ)搭建起來(lái)的架構(gòu)比以細(xì)節(jié)為基礎(chǔ)搭建起來(lái)的架構(gòu)要穩(wěn)定的多。在java中,抽象指的是接口或者抽象類(lèi),細(xì)節(jié)就是具體的實(shí)現(xiàn)類(lèi),使用接口或者抽象類(lèi)的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類(lèi)去完成。
依賴(lài)倒置原則的核心思想是面向接口編程,我們依舊用一個(gè)例子來(lái)說(shuō)明面向接口編程比相對(duì)于面向?qū)崿F(xiàn)編程好在什么地方。場(chǎng)景是這樣的,母親給孩子講故事,只要給她一本書(shū),她就可以照著書(shū)給孩子講故事了。代碼如下:
class Book{
public String getContent(){
return "很久很久以前有一個(gè)阿拉伯的故事……";
}
}
class Mother{
public void narrate(Book book){
System.out.println("媽媽開(kāi)始講故事");
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é)果:
媽媽開(kāi)始講故事
很久很久以前有一個(gè)阿拉伯的故事……
運(yùn)行良好,假如有一天,需求變成這樣:不是給書(shū)而是給一份報(bào)紙,讓這位母親講一下報(bào)紙上的故事,報(bào)紙的代碼如下:
class Newspaper{
public String getContent(){
return "林書(shū)豪38+7領(lǐng)導(dǎo)尼克斯擊敗湖人……";
}
}
這位母親卻辦不到,因?yàn)樗尤徊粫?huì)讀報(bào)紙上的故事,這太荒唐了,只是將書(shū)換成報(bào)紙,居然必須要修改Mother才能讀。假如以后需求換成雜志呢?換成網(wǎng)頁(yè)呢?還要不斷地修改Mother,這顯然不是好的設(shè)計(jì)。原因就是Mother與Book之間的耦合性太高了,必須降低他們之間的耦合度才行。
我們引入一個(gè)抽象的接口IReader。讀物,只要是帶字的都屬于讀物:
interface IReader{
public String getContent();
}
Mother類(lèi)與接口IReader發(fā)生依賴(lài)關(guān)系,而B(niǎo)ook和Newspaper都屬于讀物的范疇,他們各自都去實(shí)現(xiàn)IReader接口,這樣就符合依賴(lài)倒置原則了,代碼修改為:
class Newspaper implements IReader {
public String getContent(){
return "林書(shū)豪17+9助尼克斯擊敗老鷹……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一個(gè)阿拉伯的故事……";
}
}
class Mother{
public void narrate(IReader reader){
System.out.println("媽媽開(kāi)始講故事");
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é)果:
媽媽開(kāi)始講故事
很久很久以前有一個(gè)阿拉伯的故事……
媽媽開(kāi)始講故事
林書(shū)豪17+9助尼克斯擊敗老鷹……
這樣修改后,無(wú)論以后怎樣擴(kuò)展Client類(lèi),都不需要再修改Mother類(lèi)了。這只是一個(gè)簡(jiǎn)單的例子,實(shí)際情況中,代表高層模塊的Mother類(lèi)將負(fù)責(zé)完成主要的業(yè)務(wù)邏輯,一旦需要對(duì)它進(jìn)行修改,引入錯(cuò)誤的風(fēng)險(xiǎn)極大。所以遵循依賴(lài)倒置原則可以降低類(lèi)之間的耦合性,提高系統(tǒng)的穩(wěn)定性,降低修改程序造成的風(fēng)險(xiǎn)。
采用依賴(lài)倒置原則給多人并行開(kāi)發(fā)帶來(lái)了極大的便利,比如上例中,原本Mother類(lèi)與Book類(lèi)直接耦合時(shí),Mother類(lèi)必須等Book類(lèi)編碼完成后才可以進(jìn)行編碼,因?yàn)镸other類(lèi)依賴(lài)于Book類(lèi)。修改后的程序則可以同時(shí)開(kāi)工,互不影響,因?yàn)镸other與Book類(lèi)一點(diǎn)關(guān)系也沒(méi)有。參與協(xié)作開(kāi)發(fā)的人越多、項(xiàng)目越龐大,采用依賴(lài)導(dǎo)致原則的意義就越重大。現(xiàn)在很流行的TDD開(kāi)發(fā)模式就是依賴(lài)倒置原則最成功的應(yīng)用。
傳遞依賴(lài)關(guān)系有三種方式,以上的例子中使用的方法是接口傳遞,另外還有兩種傳遞方式:構(gòu)造方法傳遞和setter方法傳遞,相信用過(guò)Spring框架的,對(duì)依賴(lài)的傳遞方式一定不會(huì)陌生。
在實(shí)際編程中,我們一般需要做到如下3點(diǎn):
低層模塊盡量都要有抽象類(lèi)或接口,或者兩者都有。
變量的聲明類(lèi)型盡量是抽象類(lèi)或接口。
使用繼承時(shí)遵循里氏替換原則。
依賴(lài)倒置原則的核心就是要我們面向接口編程,理解了面向接口編程,也就理解了依賴(lài)倒置。
更多信息請(qǐng)查看IT技術(shù)專(zhuān)欄