DIP 是軟體架構中最具深度的原則之一,它告訴我們: 「靈活的系統,其原始碼依賴關係應該只涉及抽象(Abstract),而不涉及具體(Concrete)。」

在靜態型別語言(如 Java)中,這意味著 import, use, include 指令只應該指向包含介面(Interface)或抽象類別(Abstract Class)的模組,而不該指向具體的實作類別。

一、穩定抽象 vs. 易變具體#

為什麼要依賴抽象?

  • 穩定性(Stable): 抽象介面通常比具體實作穩定。修改一個介面通常意味著要修改所有實作;但修改一個實作,通常不需要修改介面
  • 易變性(Volatile): 具體類別包含了運作細節,這正是系統中變動最頻繁的部分

架構師的目標是:依賴那些穩定的東西,避開那些易變的東西。

二、DIP 的四大編碼規範#

為了落實 DIP,作者提出了四個具體編碼建議:

規範說明
不參考易變的具體類別改為參考抽象介面;促成「抽象工廠」模式的使用
不從易變的具體類別衍生繼承是最強、難修改的依賴關係;應謹慎使用
不覆寫具體函數具體函數包含依賴,覆寫後原始依賴仍存在;應宣告為抽象
不提到任何具體和易變名稱上述原則的總結

三、解決物件建立問題:抽象工廠#

這條原則最難遵守的地方在於「物件建立」。
為執行程式,我們終究需要 new 一個具體物件實例(這會產生對具體類別的依賴)。

為了解決這矛盾,我們使用 抽象工廠模式 (Abstract Factory Pattern)

  • 控制流程: Application ➡️ 呼叫 ➡️ ServiceFactoryImpl ➡️ 建立 ➡️ ConcreteImpl
  • 依賴方向: Application ➡️ 依賴 ➡️ ServiceFactory (介面)
  • 結果: 原始碼的依賴與控制流程反向。這邊界將系統切為兩半:抽象層(高層)與具體層(低層)

Figure 11.1: Use of the Abstract Factory pattern

四、Main 元件:必要的髒污#

我們無法 100% 消除對具體類別的依賴。總有人負責啟動系統並建立第一個物件。
這個地方就是 Main 函式

  • 集中髒污: Main 是系統中違反 DIP 的特例。它負責實例化所有的工廠、策略與具體類別,
    然後將它們交給高層的抽象模組
  • 架構意義: 將所有「骯髒」依賴集中在 Main 這邊界上,
    從而讓系統其他部分保持潔淨(只依賴抽象)

DIP 是架構的基礎。
如果依賴方向沒有反轉(指向抽象),高層策略就會受制於低層細節,導致系統脆弱且難以維護。