目標:撰寫可維護的程式碼#
Dependency Injection 的終極目標不是引入花俏的技術,而是撰寫可維護的程式碼 (Maintainable Code)。DI 是達成鬆耦合 (Loose Coupling) 的一組技術與原則,而鬆耦合正是讓軟體在長期演進中保持可維護性的關鍵。
破除四個常見迷思#
許多開發者對 DI 存在誤解,書中逐一破除:
- 「DI 只用於 Late Binding」——錯。Late Binding(例如透過設定檔切換實作)只是 DI 的好處之一,並非唯一目的。
- 「DI 只用於 Unit Testing」——錯。可測試性是重要好處,但 DI 帶來的價值遠不止於此。
- 「DI 是一種強化版的 Abstract Factory」——錯。DI 是一種設計模式與原則的集合,不是一個特定的 Factory 模式。
- 「DI 需要 DI Container」——錯。完全可以用 Pure DI(手動組裝物件圖)來實踐 DI,不依賴任何框架。
DI Container 是可選的工具,不是 DI 的必要條件。本書前三部分完全使用 Pure DI,第四部分才介紹 DI Container。
電線插座比喻#
書中用日常生活中的電線插座系統來類比 DI 的設計原則:
- 插頭與插座 = Liskov Substitution Principle (LSP):只要符合介面規格,任何裝置都能插上使用

Figure 1.4: 透過插座與插頭,吹風機可以與牆壁插座鬆耦合

Figure 1.5: 使用插座與插頭,可以將吹風機替換為電腦——對應 Liskov Substitution Principle
- 安全插座 (Safety Plug) = Null Object Pattern:提供一個「什麼都不做」的預設實作
- UPS 不斷電系統 = Decorator Pattern:在不改變原有行為的前提下,包裹額外功能

Figure 1.7: 加入 UPS 不斷電系統以防斷電——對應 Decorator 設計模式
- 電源延長線 (Power Strip) = Composite Pattern:將多個實作組合為一個

Figure 1.8: 延長線讓多個電器共用一個插座——對應 Composite 設計模式
- 轉接頭 (Adapter) = Adapter Pattern:讓不相容的介面得以合作
Hello DI! 範例#
書中以一個最簡範例說明 DI 的基本結構:
public class Salutation
{
private readonly IMessageWriter writer;
public Salutation(IMessageWriter writer)
{
this.writer = writer;
}
public void Exclaim()
{
writer.Write("Hello DI!");
}
}IMessageWriter是 Abstraction(介面)ConsoleMessageWriter是具體實作,將訊息寫到 ConsoleSalutation透過 Constructor Injection 接收依賴,完全不知道具體實作是什麼

Figure 1.10: Hello DI! 應用程式中各協作者的關係
DI 的五大好處#
| 好處 | 說明 |
|---|---|
| Late Binding | 不需重新編譯即可替換元件實作 |
| Extensibility | 透過新增程式碼(而非修改)來擴充功能 |
| Parallel Development | 團隊成員可以針對介面各自開發 |
| Maintainability | 類別職責單一,容易理解與修改 |
| Testability | 可注入 Test Double 進行隔離測試 |
Stable vs Volatile Dependencies#
區分依賴類型是決定是否需要注入的關鍵判斷:
Stable Dependencies#
符合以下條件的依賴屬於穩定依賴,通常不需要透過 DI 注入:
- 已經可用(如 .NET BCL 中的類別)
- 行為確定性高 (Deterministic)
- 不需要特別的環境設定
- 不會有需要替換的情境
Volatile Dependencies#
符合以下任一條件即為揮發性依賴,應該透過 DI 注入:
- 仍在開發中,尚未穩定
- 不是在所有環境都可用(如資料庫、外部服務)
- 行為不確定 (Nondeterministic),如時間、亂數
- 未來需要替換或包裹額外行為
判斷是否該注入一個依賴時,問自己:「這個依賴在測試環境或其他部署環境中會不會需要替換?」如果答案是肯定的,它就是 Volatile Dependency。
DI 的三個維度#
DI 涵蓋三個核心維度,每個維度處理不同的關注點:
- Object Composition——如何組裝物件圖 (Object Graph)?哪個物件依賴哪個物件?
- Object Lifetime——每個依賴的生命週期為何?Singleton? Transient? Scoped?
- Interception——如何在不修改原有程式碼的情況下,攔截並擴充行為?

Figure 1.12: 攔截 ConsoleMessageWriter
關鍵定義#
Pure DI
不使用任何 DI Container,完全以手動方式在 Composition Root 中組裝物件圖的做法。這是理解 DI 最直接的方式。
Constructor Injection
透過建構函式的參數來宣告並接收依賴。這是最常見、最推薦的 DI 模式。
Module
一個可部署的邏輯單元(通常對應一個 Assembly 或 Package),用來組織相關的類別與介面。DI 的設計很大程度取決於 Module 之間的依賴方向。