基本知識
1.依賴倒置原則(DIP)是面向對象設計(OOD)中的五個基本原則之一,通常被稱為 SOLID 原則中的 D
2.核心思想:
高層模塊不應該依賴低層模塊,兩者都應該依賴抽象。 (High-level modules should not depend on low-level modules. Both should depend on abstractions.)
抽象不應該依賴于細節,細節應該依賴于抽象。 (Abstractions should not depend on details. Details should depend on abstractions.)
簡單來說,DIP 提倡的是在軟件設計中,我們應該讓依賴關系指向抽象(如接口或抽象類),而不是具體的實現(如具體的類)。這顛覆了傳統上高層模塊直接依賴低層模塊的“自上而下”的依賴關系,從而實現了“倒置”的效果。
3.依賴:
“依賴”指的是一個軟件構件(例如一個類、一個模塊、一個組件或一個系統)需要另一個構件才能正常工作或完成其功能。
常見四種依賴:
1.編譯時依賴:個類 A 的代碼中引用了類 B 的成員變量、方法、常量、類型定義(如作為參數類型、返回類型、局部變量類型、父類或接口)
class B {public void doSomething() { /* ... */ }
}class A {private B bInstance; // A 依賴 B 作為成員變量public void methodA(B param) { // A 依賴 B 作為方法參數B localB = new B(); // A 依賴 B 作為局部變量和構造函數param.doSomething(); // A 依賴 B 的方法}
}
2.運行時依賴:
即使在編譯時沒有直接引用,但在程序運行時,構件 A 可能需要構件 B 提供的服務或數據。例如:反射時候A需要使用B的方法,雖然編譯A的時候不需要B,但是運行時候B需要存在。又例如:配置文件
3.直接依賴和間接依賴:構件A直接引用構件B稱為直接依賴,而構件A依賴構件B,構件B依賴構件C,此為間接依賴
4.接口依賴和實現依賴:
接口依賴 Interface Dependency:一個構件依賴另一個構件的接口
實現依賴 Implementation Dependency:一個構件依賴另一個構件的具體實現類。這種會造成緊耦合,DIP正是為了避免這種依賴。
例子:
class A { private List<String> list; }:A 依賴于 List 接口(接口依賴)。
class A { private ArrayList<String> list; }:A 依賴于 ArrayList 具體實現類(實現依賴)
具體實例
1.沒有實現DIP的情況
即傳統的高層模塊直接依賴低層模塊
public class Bike {public void take(){System.out.println("騎自行車");}
}
public class Car {public void run(){System.out.println("開車");}
}
public class Human {private Bike bike;private Car car;public Human(){this.bike=new Bike();this.car=new Car();}public void driverCar(){this.car.run();}public void takeBike(){this.bike.take();}
}
public class Test {public static void main(String[] args) {Human human=new Human();human.driverCar();human.takeBike();}
}
高層模塊Human類依賴Bike類和Car類,在human類中聲明了兩個成員變量,分別時Bike類和Car類,并在構造方法中實例化了Bike類和Car,這些都明顯違反了DIP,有強耦合情況。
那么違反DIP的問題:
1.難以擴展,比如我們后面如果引入另外的交通工具,比如MotoCycle,或者Bus,或者Subway,那么我們必須修改Human類,添加對應的成員變量,然后進行實例化,再寫新的方法。
2.難以測試,如果想單獨測試 Human 類(例如,測試 driverCar 方法的邏輯,但不真正涉及到實際的汽車運行),很難對 Bike 和 Car 進行模擬或替換,因為它們在 Human 內部被硬編碼創建了。
2.使用DIP
public interface ITransportation {void take();
}
public class Bike implements ITransportation{@Overridepublic void take() {System.out.println("騎自行車");}
}
public class Bus implements ITransportation{@Overridepublic void take() {System.out.println("坐汽車");}
}
public class Car implements ITransportation{@Overridepublic void take() {System.out.println("開車");}
}
public class Human {private ITransportation transportation;//依賴抽象接口/*通過構造方法注入依賴*/public Human(ITransportation iTransportation){this.transportation=iTransportation;}public void take(){transportation.take();}}
ublic class Test {public static void main(String[] args) {Bike bike = new Bike();Human human = new Human(bike);human.take();Bus bus =new Bus();Human human1=new Human(bus);human1.take();Car car = new Car();Human human2 = new Human(car);human2.take();}
}
這段代碼中,我們使用ITransportation 接口。這是一個抽象,定義了“交通工具”應該具備的通用行為 take()。
低層模塊依賴抽象:Bike, Bus, Car 類。這三個類是具體的低層實現(細節)。
它們都實現了 ITransportation 接口。這意味著它們現在依賴于這個抽象來提供其具體功能。它們不是獨立的,而是受 ITransportation 接口定義的約束。
高層模塊依賴抽象:Human 類
Human 類是高層模塊,它需要使用交通工具。
private ITransportation transportation;
:Human 類內部聲明了一個 ITransportation 類型的成員變量,而不是具體的 Bike、Bus 或 Car 類型。這表明 Human 依賴于抽象。
依賴注入(通過構造函數):
public Human(ITransportation iTransportation){this.transportation=iTransportation;
}
Human 不再在自己的內部(比如構造函數中)實例化具體的交通工具。相反,它通過構造函數接收一個已經創建好的 ITransportation 實例。這是一種經典的依賴注入方式。Human 不知道它會具體操作的是自行車、巴士還是汽車,它只知道如何與任何實現了 ITransportation 接口的對象進行交互。
而“細節應該依賴于抽象” 這句話,在代碼例子中,正是對應 Car, Bus, Bike 實現 ITransportation 接口的情況。
細節(Details):在這里指的是 Car、Bus 和 Bike 這些具體的實現類。它們包含了實現“交通工具”功能的具體細節,比如 System.out.println(“開車”) 或 System.out.println(“騎自行車”)。
抽象(Abstractions):在這里指的是 ITransportation 接口。它定義了一個通用的契約:任何實現了它的類都必須提供一個 take() 方法。
“依賴于”:通過 implements 關鍵字,Car、Bus 和 Bike 在編譯時就被強制要求遵循 ITransportation 接口的規范。它們的實現必須符合接口的定義。如果它們不實現 take() 方法,或者方法簽名不正確,編譯器就會報錯。
倒置”的效果:
在舊的代碼中:Human (高層) -> Bike (低層), Human (高層) -> Car (低層)。
在這段代碼中:
Human (高層) -> ITransportation (抽象)。
Bike, Bus, Car (低層) -> ITransportation (抽象)。
依賴關系不再是高層指向具體的低層,而是高層和低層都指向了同一個抽象。這正是依賴關系的“倒置”。
回顧下
回顧最初的兩句話:
高層模塊不應該依賴低層模塊,兩者都應該依賴抽象。
抽象不應該依賴于細節,細節應該依賴于抽象。
顯然,我們看到Human這個高層模塊里面有抽象接口的成員變量,故高層模塊依賴抽象。對于低層模塊Car,Bus,Bike。我們可以看到“一個類 A 的代碼中引用了類 B 的…接口”中對應上:A是Car類,B是ITransportation 接口。即A引用B類型作為接口
Car 類的代碼中,明確地使用了 ITransportation 這個接口類型:
public class Car implements ITransportation
所以低層模塊也是依賴抽象的。這種依賴是通過 implements 關鍵字在編譯時建立的,它強制 Car 去遵守 ITransportation 定義的契約
符合 DIP 帶來的好處:
降低耦合度: Human 類不再知道任何關于 Bike、Bus 或 Car 具體實現的信息。它只關心 ITransportation 接口。
提高可擴展性: 如果將來需要增加新的交通工具(例如 Train, Airplane),只需要創建新的類實現 ITransportation 接口即可,無需修改 Human 類的任何代碼。這完全符合開閉原則。
提高可測試性: 在測試 Human 類時,可以很容易地創建 ITransportation 接口的模擬(Mock)實現,以便隔離測試 Human 自身的邏輯,而無需依賴真實的交通工具。
提高可維護性: 交通工具的實現細節變化不會影響到 Human 類。