介紹
SOLID 是由 Robert Cecil Martin (Uncle Bob) 等人倡議的五項核心原則。
它們並非刻板的硬性規定,而更像是一種精神指引,幫助開發者建立易於維護、具備彈性的系統:
- SRP:單一職責原則 (Single Responsibility Principle)
- OCP:開放封閉原則 (Open-Closed Principle)
- LSP:里氏替換原則 (Liskov Substitution Principle)
- ISP:介面隔離原則 (Interface Segregation Principle)
- DIP:依賴反轉原則 (Dependency Inversion Principle)
本篇將聚焦於最後一個原則:依賴反轉原則 (DIP)。
Dependency Inversion Principle
- 高階模組(核心邏輯)不應依賴低階模組(細節實作),兩者都應依賴抽象。 (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.)
我們似乎又換了一個說法,講著同樣的事
Programming to an abstraction, not an implementation
程式舉例:依賴、持有、組合?
在探討反轉之前,我們需要先釐清什麼是「依賴」。在物件導向設計中,物件之間的關係強度各有不同。
依賴 (Dependency - “uses-a”)
// 依賴的範例
public class A {
public void call() {/*do something*/};
}
public class B {
public void call(A inj) {
A.call();
};
}
當一個類別的方法使用了另一個類別,但沒有將其儲存為狀態,這是一種較弱的關聯。
聚合 / 持有 (Aggregation - “has-a”)
// 持有的範例
public class B {
private A myA;
public B (A inj) {
this.myA = inj;
}
public void call() {
this.myA.call();
}
}
這時候是依賴嗎?還是?
Ans: 這是 “has-a” 持有(has-a)是更強的依賴
持有,多數情況下也被稱作,聚合(aggregation)
組合 (Composition - “contains-a”)
// 組合的範例
public class B {
final private myA = new A();
public void call() {
this.myA.call();
}
}
這是 “composition”
組合的依賴,影響生命週期 B 不僅擁有 A,還負責 A 的創建與銷毀。
試問:這是什麼關聯?
刀在人在,刀不在人不在
程式範例:依賴反轉
什麼才是依賴反轉?
我們將原本的程式調整一下,來作說明
public interface Contract {
public void call();
}
// A 相對不穩(實作)
public class A implements Contract {
@Override
public void call() { /* do something */ }
}
// B 相對穩定(控制)
public class B {
private Contract myContract;
public B (Contract inj) {
this.myContract = inj;
}
public void call() {
this.myContract.call();
}
}
在這個結構中,A 類別與 B 類別同時依賴 Contract 這個抽象介面。
Class B 不再關心 A 的具體實作,
只要 Contract 這個「抽象」保持不變,B 就能穩定運作。
除非有跳脫抽象意義的實作(違反 LSP),
這就是依賴反轉。
結論
DIP 關注不僅只有依賴強度,更重要的是:
軟體是否依賴於不穩定的細節?還是依賴於穩定的抽象?
思考
抽象是什麼?
- 對我而言,「抽象」是軟體工程裡「說好的規範與合約」。
維持軟體穩定的「抽象」,只有在只有在程式碼層級中嗎?
參考資料
- Clean Architecture - Robert C. Martin
