依赖倒置原则(Dependecy-Inversion Principle)
依賴倒置原則(Dependence Inversion Principle,DIP)的原始定義:
- 高層模塊不應(yīng)該依賴底層模塊,兩者都應(yīng)該依賴其抽象;
- 抽象不應(yīng)該依賴細(xì)節(jié);
- 細(xì)節(jié)應(yīng)該依賴抽象。
抽象:即抽象類或接口,兩者是不能夠?qū)嵗摹?/strong>
細(xì)節(jié):即具體的實現(xiàn)類,實現(xiàn)接口或者繼承抽象類所產(chǎn)生的類,兩者可以通過關(guān)鍵字new直接被實例化。
依賴倒置原則在Java語言中的表現(xiàn)是:
- 模塊間的依賴通過抽象發(fā)生,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或者抽象類產(chǎn)生的;
- 接口或抽象類不依賴于實現(xiàn)類;
- 實現(xiàn)類依賴接口或抽象類。
一、什么是依賴倒置原則
一種表述:
抽象不應(yīng)當(dāng)依賴于細(xì)節(jié);細(xì)節(jié)應(yīng)當(dāng)依賴于抽象。
另一種表述:
要針對接口編程,不要針對實現(xiàn)編程。
針對接口編程的意思就是說,應(yīng)當(dāng)使用Java接口和抽象Java類進(jìn)行變量的類型聲明、參量的類型聲明、方法的返回類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等。
不要針對實現(xiàn)編程的意思就是說,不應(yīng)當(dāng)使用具體Java類進(jìn)行變量的類型聲明、參量的類型聲明、方法的返回類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等。
其核心思想是:依賴于抽象。具體而言就是高層模塊不依賴于底層模塊,二者都同依賴于抽象;抽象不依賴于具體,具體依賴于抽象。
高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。
(High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.)
高層模塊不應(yīng)該依賴低層模塊,即高層模塊應(yīng)該持有抽象類或接口的引用,而不應(yīng)該持有某一具體實現(xiàn)類的引用。
抽象不應(yīng)該依賴細(xì)節(jié),即接口或抽象類不應(yīng)該持有某一具體實現(xiàn)類的引用,而應(yīng)該持有此類所繼承抽象類或所實現(xiàn)接口的引用。
細(xì)節(jié)應(yīng)該依賴抽象,即實現(xiàn)類也應(yīng)該持有抽象類或接口的引用,而不應(yīng)該持有某一具體實現(xiàn)類的引用。
我們知道,依賴一定會存在于類與類、模塊與模塊之間。當(dāng)兩個模塊之間存在緊密的耦合關(guān)系時,最好的方法就是分離接口和實現(xiàn):在依賴之間定義一個抽象的接口使得高層模塊調(diào)用接口,而底層模塊實現(xiàn)接口的定義,以此來有效控制耦合關(guān)系,達(dá)到依賴于抽象的設(shè)計目標(biāo)。
抽象的穩(wěn)定性決定了系統(tǒng)的穩(wěn)定性,因為抽象是不變的,依賴于抽象是面向?qū)ο笤O(shè)計的精髓,也是依賴倒置原則的核心。
依賴于抽象是一個通用的原則,而某些時候依賴于細(xì)節(jié)則是在所難免的,必須權(quán)衡在抽象和具體之間的取舍,方法不是一層不變的。依賴于抽象,就是對接口編程,不要對實現(xiàn)編程。
依賴倒置原則基于這樣一個事實:相對于細(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)類去完成。
依賴倒置原則的核心思想是面向接口編程。該原則規(guī)定:
該原則顛倒了一部分人對于面向?qū)ο笤O(shè)計的認(rèn)識方式。如高層次和低層次對象都應(yīng)該依賴于相同的抽象接口。
應(yīng)用依賴反轉(zhuǎn)原則同樣被認(rèn)為是應(yīng)用了[適配器模式],例如:高層的類定義了它自己的適配器接口(高層類所依賴的抽象接口)。被適配的對象同樣依賴于適配器接口的抽象(這是當(dāng)然的,因為它實現(xiàn)了這個接口),同時它的實現(xiàn)則可以使用它自身所在低層模塊的代碼。通過這種方式,高層組件則不依賴于低層組件,因為它(高層組件)僅間接的通過調(diào)用適配器接口多態(tài)方法使用了低層組件,而這些多態(tài)方法則是由被適配對象以及它的低層模塊所實現(xiàn)的。
依賴倒置與依賴正置
依賴正置就是類間的依賴是實實在在的實現(xiàn)類間的依賴,也就是面向?qū)崿F(xiàn)編程,這也是正常人的思維方式,我要開奔馳車就依賴奔馳車,我要使用筆記本電腦就直接依賴筆記本電腦,而編寫程序需要的是對現(xiàn)實世界的事物進(jìn)行抽象,抽象的結(jié)構(gòu)就是有了抽象類和接口,然后我們根據(jù)系統(tǒng)設(shè)計的需要產(chǎn)生了抽象間的依賴,代替了人們傳統(tǒng)思維中的事物間的依賴,“倒置”就是從這里產(chǎn)生的。
依賴注入
依賴注入就是將實例變量傳入到一個對象中去(Dependency injection means giving an object its instance variables)。
什么是依賴
如果在 Class A 中,有 Class B 的實例,則稱 Class A 對 Class B 有一個依賴。例如下面類 Human 中用到一個 Father 對象,我們就說類 Human 對類 Father 有一個依賴。
依賴注入
依賴注入是這樣的一種行為,在類Car中不主動創(chuàng)建GasEnergy的對象,而是通過外部傳入GasEnergy對象形式來設(shè)置依賴。 常用的依賴注入有如下三種方式
構(gòu)造器注入
將需要的依賴作為構(gòu)造方法的參數(shù)傳遞完成依賴注入。
Setter方法注入
增加setter方法,參數(shù)為需要注入的依賴亦可完成依賴注入。
接口注入
接口注入,聞其名不言而喻,就是為依賴注入創(chuàng)建一套接口,依賴作為參數(shù)傳入,通過調(diào)用統(tǒng)一的接口完成對具體實現(xiàn)的依賴注入。
接口注入和setter方法注入類似,不同的是接口注入使用了統(tǒng)一的方法來完成注入,而setter方法注入的方法名稱相對比較隨意。
在實現(xiàn)依賴倒轉(zhuǎn)原則時,我們需要針對抽象層編程,而將具體類的對象通過依賴注入(DependencyInjection, DI)的方式注入到其他對象中,依賴注入是指當(dāng)一個對象要與其他對象發(fā)生依賴關(guān)系時,通過抽象來注入所依賴的對象。常用的注入方式有三種,分別是:構(gòu)造注入,設(shè)值注入(Setter注入)和接口注入。構(gòu)造注入是指通過構(gòu)造函數(shù)來傳入具體類的對象,設(shè)值注入是指通過Setter方法來傳入具體類的對象,而接口注入是指通過在接口中聲明的業(yè)務(wù)方法來傳入具體類的對象。這些方法在定義時使用的是抽象類型,在運行時再傳入具體類型的對象,由子類對象來覆蓋父類對象。
依賴注入
仔細(xì)看這段代碼我們會發(fā)現(xiàn)存在一些問題:
(1). 如果現(xiàn)在要改變 father 生成方式,如需要用new Father(String name)初始化 father,需要修改 Human 代碼;
(2). 如果想測試不同 Father 對象對 Human 的影響很困難,因為 father 的初始化被寫死在了 Human 的構(gòu)造函數(shù)中;
(3). 如果new Father()過程非常緩慢,單測時我們希望用已經(jīng)初始化好的 father 對象 Mock 掉這個過程也很困難。
上面代碼中,我們將 father 對象作為構(gòu)造函數(shù)的一個參數(shù)傳入。在調(diào)用 Human 的構(gòu)造方法之前外部就已經(jīng)初始化好了 Father 對象。像這種非自己主動初始化依賴,而通過外部來傳入依賴的方式,我們就稱為依賴注入。
現(xiàn)在我們發(fā)現(xiàn)上面 1 中存在的兩個問題都很好解決了,簡單的說依賴注入主要有兩個好處:
(1). 解耦,將依賴之間解耦。
(2). 因為已經(jīng)解耦,所以方便做單元測試,尤其是 Mock 測試。
3、什么是倒置
到了這里,我們對依賴倒置原則的“依賴”就很好理解了,但是什么是“倒置”呢。是這樣子的,剛開始按照正常人的一般思維方式,我想吃香蕉就是吃香蕉,想吃蘋果就吃蘋果,編程也是這樣,都是按照面向?qū)崿F(xiàn)的思維方式來設(shè)計。而現(xiàn)在要倒置思維,提取公共的抽象,面向接口(抽象類)編程。不再依賴于具體實現(xiàn)了,而是依賴于接口或抽象類,這就是依賴的思維方式“倒置”了。
4、依賴的三種實現(xiàn)方式
對象的依賴關(guān)系有三種方式來傳遞:
//人接口 public interface People {public void eat(Fruit fruit);//人都有吃的方法,不然都餓死了 } //水果接口 public interface Fruit {public String getName();//水果都是有名字的 } //具體Jim人類 public class Jim implements People{public void eat(Fruit fruit){System.out.println("Jim eat " + fruit.getName());} } //具體蘋果類 public class Apple implements Fruit{public String getName(){return "apple";} } //具體香蕉類 public class Banana implements Fruit{public String getName(){return "banana";} } public class Client {public static void main(String[] args) {People jim = new Jim();Fruit apple = new Apple();Fruit Banana = new Banana(); //這里符合了里氏替換原則jim.eat(apple);jim.eat(Banana);} }Client類是復(fù)雜的業(yè)務(wù)邏輯,屬于高層模塊,而People和Fruit是原子模塊,屬于低層模塊。Client依賴于抽象的People和Fruit接口,這就做到了:高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴于抽象(抽象類或接口)。
Client不僅依賴于接口Fruit,還依賴于具體的實現(xiàn)Apple了。
People和Fruit接口與各自的實現(xiàn)類沒有關(guān)系,增加實現(xiàn)類不會影響接口,這就做到了:抽象(抽象類或接口)不應(yīng)該依賴于細(xì)節(jié)(具體實現(xiàn)類)。
Jim、Apple、Banana實現(xiàn)類都要去實現(xiàn)各自的接口所定義的抽象方法,所以是依賴于接口的。這就做到了:細(xì)節(jié)(具體實現(xiàn)類)應(yīng)該依賴抽象。
“依賴于抽象(或者叫接口)”這個說法是對的,只是這段代碼Fruit apple = new Apple();不能去new具體的實現(xiàn),應(yīng)該用工廠方法來生產(chǎn)出具體的實現(xiàn),這樣就可以做到只依賴于接口了。
接口方法中聲明依賴對象。就是我們上面代碼所展示的那樣。
而接口注入是指通過在接口中聲明的業(yè)務(wù)方法來傳入具體類的對象。這些方法在定義時使用的是抽象類型,在運行時再傳入具體類型的對象,由子類對象來覆蓋父類對象。
構(gòu)造方法傳遞依賴對象。在構(gòu)造函數(shù)中的需要傳遞的參數(shù)是抽象類或接口的方式實現(xiàn)。代碼如下:
//具體Jim人類 public class Jim implements People{private Fruit fruit;public Jim(Fruit fruit){//構(gòu)造方法傳遞依賴對象this.fruit = fruit;}public void eat(Fruit fruit){System.out.println("Jim eat " + this.fruit.getName());} }Setter方法傳遞依賴對象。在我們設(shè)置的setXXX方法中的參數(shù)為抽象類或接口,來實現(xiàn)傳遞依賴對象。代碼如下:
//具體Jim人類 public class Jim implements People{private Fruit fruit;public void setFruit(Fruit fruit){//setter方式傳遞依賴對象this.fruit = fruit;}public void eat(){System.out.println("Jim eat " + this.fruit.getName());} }5、優(yōu)點
從上面的代碼修改過程中,我們可以看到由于類之間松耦合的設(shè)計,面向接口編程依賴抽象而不依賴細(xì)節(jié),所以在修改某個類的代碼時,不會牽涉到其他類的修改,顯著降低系統(tǒng)風(fēng)險,提高系統(tǒng)健壯性。
還有一個優(yōu)點是,在我們實際項目開發(fā)中,都是多人團(tuán)隊協(xié)作,每人負(fù)責(zé)某一模塊。比如一個人負(fù)責(zé)開發(fā)People模塊,一人負(fù)責(zé)開發(fā)Fruit模塊,如果未采用依賴倒置原則,沒有提取抽象,那么開發(fā)People模塊的人必須等Fruit模塊開發(fā)完成后自己才能開發(fā),否則編譯都無法通過,這就是單線程的開發(fā)。為了能夠兩人并行開發(fā),設(shè)計時遵循依賴倒置原則,提取抽象,就可以大大提高開發(fā)進(jìn)度。
采用依賴倒置原則可以減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性,降低并行開發(fā)引起的風(fēng)險,提高代碼的可讀性和可維護(hù)性。
6、總結(jié)
依賴倒置原則實際上就是要求“面向接口編程”。
說到底,依賴倒置原則的核心就是面向接口編程的思想,盡量對每個實現(xiàn)類都提取抽象和公共接口形成接口或抽象類,依賴于抽象而不要依賴于具體實現(xiàn)。依賴倒置原則的本質(zhì)其實就是通過抽象(抽象類或接口)使各個類或模塊的實現(xiàn)彼此獨立,不相互影響,實現(xiàn)模塊間的松耦合。但是這個原則也是6個設(shè)計原則中最難以實現(xiàn)的了,如果沒有實現(xiàn)這個原則,那么也就意味著開閉原則(對擴(kuò)展開放,對修改關(guān)閉)也無法實現(xiàn)。
首先要明白所有原則都是為了達(dá)到面向?qū)ο笤O(shè)計的可擴(kuò)展可復(fù)用可維護(hù)性而出現(xiàn)的…… 開閉原則是目的:一個已有的代碼模塊,需要很容易增加新的擴(kuò)展功能(可擴(kuò)展性),這個已有模塊需要是對外開放的;為了使已有模塊可以復(fù)用(可復(fù)用性),已有模塊需要是獨立的(單一職責(zé),高內(nèi)聚,不與其他模塊耦合在一起),同時為了方便維護(hù)(可維護(hù)性),已有模塊最好不要對原有代碼進(jìn)行修改,也就是需要是對內(nèi)封閉的;實現(xiàn)了開閉原則的設(shè)計,就達(dá)到面向?qū)ο笤O(shè)計可擴(kuò)展可復(fù)用可維護(hù)性的目的,所以說開閉原則是目的! 里氏代換原則是基礎(chǔ):通過針對抽象基類編程(業(yè)務(wù)邏輯關(guān)系的建立),具體運行時代換具體子類對象執(zhí)行,可以達(dá)到開閉原則的目的,該實現(xiàn)過程就是里氏代換原則定義本身,所以說里氏代換原則是理論基礎(chǔ)! 依賴倒轉(zhuǎn)原則是手段:牛人們總結(jié)了實現(xiàn)里氏代換原則的方法,抽象不依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象的依賴倒轉(zhuǎn)原則。具體就是變量、參數(shù)、方法返回、數(shù)據(jù)類型轉(zhuǎn)換等都要用抽象定義聲明,再通過依賴注入(構(gòu)造注入、設(shè)值注入和接口注入)的方式將具體對象注入到有依賴關(guān)系的對象中。所以說依賴倒轉(zhuǎn)原則是實現(xiàn)目的手段!
開閉原則是目標(biāo),里氏代換原則是基礎(chǔ),依賴倒轉(zhuǎn)原則是手段; 第一要明確,所有的原則都是為了實現(xiàn)面向?qū)ο笤O(shè)計的可擴(kuò)展性,可復(fù)用性,可維護(hù)性(對原有代碼不要進(jìn)行修改)而定義的;一個已有的代碼模塊,需要實現(xiàn)可擴(kuò)展性,需要對外保持開放;為了實現(xiàn)可復(fù)用性,需要保持獨立(單一職責(zé),高內(nèi)聚,低耦合);為了實現(xiàn)可維護(hù)性,需要對內(nèi)封閉(對已有代碼模塊不要進(jìn)行修改)。 開閉原則(Open-Closed Principle, OCP):一個軟件實體應(yīng)當(dāng)對擴(kuò)展開放(可擴(kuò)展性),對修改關(guān)閉(可維護(hù)性)。即軟件實體應(yīng)盡量在不修改原有代碼的情況下進(jìn)行擴(kuò)展。所以說開閉原則是目的。 第二,為了實現(xiàn)開閉原則(可擴(kuò)展性,可維護(hù)性和復(fù)用性)在最初就需要對代碼模塊進(jìn)行抽象化設(shè)計(抽象化是實現(xiàn)開閉原則的關(guān)鍵);面向?qū)ο笏枷胫械某橄蠡侵赴熏F(xiàn)實中一類具有相同屬性,行為的事物歸類的方法。 抽象 ---對同一類對象的共同屬性和行為進(jìn)行概括,形成類。 有:數(shù)據(jù)抽象(屬性或狀態(tài))、代碼抽象(某類對象的共有的行為特征或功能)。抽象的實現(xiàn)是:類 大牛們就想在Java、C#等編程語言中,可以為系統(tǒng)定義一個相對穩(wěn)定的抽象層,而將不同的實現(xiàn)行為移至具體的實現(xiàn)層中完成。在很多面向?qū)ο缶幊陶Z言中都提供了接口、抽象類等機(jī)制,可以通過它們定義系統(tǒng)的抽象層,再通過具體類來進(jìn)行擴(kuò)展。如果需要修改系統(tǒng)的行為,無須對抽象層進(jìn)行任何改動,只需要增加新的具體類來實現(xiàn)新的業(yè)務(wù)功能即可,實現(xiàn)在不修改已有代碼的基礎(chǔ)上擴(kuò)展系統(tǒng)的功能,達(dá)到開閉原則的要求。具體操作作:通過針對抽象基類編程(業(yè)務(wù)邏輯關(guān)系的建立),具體運行時代換具體子類對象執(zhí)行,可以達(dá)到開閉原則的目的,該實現(xiàn)過程就是里氏代換原則定義本身,所以說里氏代換原則是理論基礎(chǔ)! 通過里氏代換原則的操操作過程,大牛們總結(jié)出抽象不依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象的依賴倒轉(zhuǎn)原則。具體就是變量、參數(shù)、方法返回、數(shù)據(jù)類型轉(zhuǎn)換等都要用抽象定義聲明,再通過依賴注入(構(gòu)造注入、設(shè)值注入和接口注入)或依賴獲取的方式將具體對象注入到有依賴關(guān)系的對象中。所以說依賴倒轉(zhuǎn)原則是實現(xiàn)目的手段! 在實現(xiàn)針對抽象編程的實踐中總結(jié)出接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不應(yīng)該依賴那些它不需要的接口。
使用依賴倒轉(zhuǎn)原則的編程方式+里氏轉(zhuǎn)換的約束=開閉原則
本質(zhì):
依賴倒置原則的本質(zhì)就是通過抽象(接口或者抽象類)使各個類或模型的實現(xiàn)彼此獨立,不互相影響,實現(xiàn)模塊間的松耦合。
規(guī)則:
每個類盡量都有接口或抽象類,或者抽象類和接口兩者都具備;
變量的表面類型盡量是接口或者抽象類;
任何類都不應(yīng)該從具體類派生;
盡量不要覆寫基類的非抽象方法;
結(jié)合里氏替換原則使用。
總結(jié)
以上是生活随笔為你收集整理的依赖倒置原则(Dependecy-Inversion Principle)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java毕业生设计高考填报信息系统计算机
- 下一篇: SpringBoot 文件管理微服务 支