依賴倒置原則
定義
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)該依賴其細(xì)節(jié)
- 細(xì)節(jié)應(yīng)該依賴抽象
高層模塊和低層模塊容易理解痕檬,每一個邏輯的實現(xiàn)都是由原子邏輯組成的暇咆,不可分割的原子邏輯就是低層模塊凯正,原子邏輯的再組裝就是高層模塊饲鄙。那什么是抽象感帅?什么又是細(xì)節(jié)呢门扇?在Java語言中雹有,==抽象就是指接口或抽象類==,兩者都是不能直接被實例化的臼寄;==細(xì)節(jié)就是實現(xiàn)類==霸奕,實現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié),其特點(diǎn)就是可以直接被實例化吉拳,也就是可以加上一個關(guān)鍵字new產(chǎn)生一個對象质帅。依賴倒置原則在Java語言中的表現(xiàn)就是:
- 模塊間的依賴通過抽象發(fā)生,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系留攒,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的煤惩;
- 接口或抽象類不依賴于實現(xiàn)類;
- 實現(xiàn)類依賴接口或抽象類炼邀。
更加精簡的定義就是“面向接口編程”——OOD(Object-Oriented Design魄揉,面向?qū)ο笤O(shè)計)的精髓之一。
依賴倒置原則優(yōu)點(diǎn)
采用依賴倒置原則可以減少類間的耦合性拭宁,提高系統(tǒng)的穩(wěn)定性什猖,降低并行開發(fā)引起的風(fēng)險,提高代碼的可讀性和可維護(hù)性红淡。
下面是源代碼
class Benz {
public void run(){
System.out.println("奔馳汽車開始運(yùn)行...");
}
}
class Driver {
public void drive(Benz benz){
benz.run();
}
}
public class Client {
public static void main(String args[]){
Benz benz = new Benz();
Driver zs = new Driver();
zs.drive(benz);
}
}
分析
通過上面的代碼不狮,知道如果需要司機(jī)重新開別的車,顯然需要改司機(jī)類在旱,汽車類摇零,這樣的開發(fā)成本是非常高的,同時也違背了依賴倒置的原則桶蝎,下面引入依賴倒置原則重新設(shè)計開汽車的方法:
抽象是對實現(xiàn)的約束驻仅,對依賴者而言谅畅,也是一種契約,不僅僅約束自己噪服,還同時約束自己與外部的關(guān)系毡泻,其目的是保證所有的細(xì)節(jié)不脫離契約的范疇,確保約束雙方按照既定的契約(抽象)共同發(fā)展粘优,只要抽象這根基線在仇味,細(xì)節(jié)就脫離不了這個圈圈,始終讓你的對象做到“言必信雹顺,行必果”丹墨。
依賴的三種寫法
1、構(gòu)造函數(shù)注入
在類中通過構(gòu)造函數(shù)聲明依賴對象嬉愧,按照依賴注入的說法贩挣,這種方式叫做構(gòu)造函數(shù)注入。
按照這種方式的注入没酣,IDriver和Driver的程序如下所示:
public interface IDriver {
public void drive();
}
public class Drive implements IDriver {
private ICar car;
public Drive(ICar car){
this.car = car;
}
public void drive() {
this.car.run();
}
}
public class Benz implements ICar{
public void run(){
System.out.println("奔馳汽車開始運(yùn)行...");
}
}
public class Drive implements IDriver {
private ICar car;
public Drive(ICar car){
this.car = car;
}
public void drive() {
this.car.run();
}
}
public class Client {
public static void main(String args[]){
ICar car = new Benz();
ICar bmw = new BMW();
IDriver zs = new Drive(car);
zs.drive();
zs = new Drive(bmw);
zs.drive();
}
}
2王财、set注入
set注入其實就是set方法注入
如下所示:
public interface IDriver {
// 注入汽車接口
public void setCar(ICar car);
public void drive();
}
public class Drive implements IDriver {
private ICar car;
// 通過set注入汽車接口
public void setCar(ICar car) {
this.car = car;
}
// 司機(jī)不用關(guān)心自己駕駛的是什么汽車
public void drive() {
this.car.run();
}
}
public class Benz implements ICar{
public void run(){
System.out.println("奔馳汽車開始運(yùn)行...");
}
}
public class Drive implements IDriver {
private ICar car;
public Drive(ICar car){
this.car = car;
}
public void drive() {
this.car.run();
}
}
public class Client {
public static void main(String args[]){
ICar benz = new Benz();
ICar bmw = new BMW();
IDriver zs = new Drive();
zs.setCar(benz);
zs.drive();
zs.setCar(bmw);
zs.drive();
}
}
3裕便、接口聲明依賴對象
剛才的分析就是接口聲明依賴對象,代碼如下所示:
public interface IDriver {
public void drive(ICar car);
}
public class Drive implements IDriver {
@Override
public void drive(ICar car) {
car.run();
}
}
public interface ICar {
void run();
}
public class BMW implements ICar{
@Override
public void run() {
System.out.println("寶馬汽車開始運(yùn)行...");
}
}
public class Benz implements ICar{
public void run(){
System.out.println("奔馳汽車開始運(yùn)行...");
}
}
public class Client {
public static void main(String args[]){
ICar benz = new Benz();
ICar bmw = new BMW();
IDriver zs = new Drive();
zs.drive(benz);
zs.drive(bmw);
}
}
總結(jié)
依賴倒置原則的本質(zhì)就是通過抽象(接口或抽象類)使各個類或模塊的實現(xiàn)彼此獨(dú)立哎垦,不互相影響墨闲,實現(xiàn)模塊間的松耦合
所以我們需要遵循以下幾點(diǎn):
- 每個類盡量都有接口或抽象類,或者抽象類和接口兩者都具備
這是依賴倒置的基本要求瞻离,接口和抽象類都是屬于抽象的,有了抽象才可能依賴倒置验辞。 - 變量的表面類型盡量是接口或者是抽象類
很多書上說變量的類型一定要是接口或者是抽象類族购,這個有點(diǎn)絕對化了撑碴,比如一個工具類伟姐,xxxUtils一般是不需要接口或是抽象類的。還有,如果你要使用類的clone方法,就必須使用實現(xiàn)類,這個是JDK提供的一個規(guī)范馅笙。 - 任何類都不應(yīng)該從具體類派生
如果一個項目處于開發(fā)狀態(tài),確實不應(yīng)該有從具體類派生出子類的情況厉亏,但這也不是絕對的董习,因為人都是會犯錯誤的,有時設(shè)計缺陷是在所難免的爱只,因此只要不超過兩層的繼承都是可以忍受的阱飘。特別是負(fù)責(zé)項目維護(hù)的同志,基本上可以不考慮這個規(guī)則,為什么沥匈?維護(hù)工作基本上都是進(jìn)行擴(kuò)展開發(fā)蔗喂,修復(fù)行為,通過一個繼承關(guān)系高帖,覆寫一個方法就可以修正一個很大的Bug缰儿,何必去繼承最高的基類呢?(當(dāng)然這種情況盡量發(fā)生在不甚了解父類或者無法獲得父類代碼的情況下散址。) - 盡量不要覆寫基類的方法
如果基類是一個抽象類乖阵,而且這個方法已經(jīng)實現(xiàn)了,子類盡量不要覆寫预麸。類間依賴的是抽象瞪浸,覆寫了抽象方法,對依賴的穩(wěn)定性會產(chǎn)生一定的影響吏祸。 - 結(jié)合里氏替換原則使用