什麼是 DI Container?#

DI Container 是一種軟體函式庫,能夠自動化 DI 的三個核心維度:

  • Object Composition——自動組裝物件圖
  • Interception——攔截並擴充行為
  • Lifetime Management——管理物件的生命週期

其核心 API 非常簡潔:透過 Resolve(Type)Resolve<T>() 方法,容器即可回傳一個完整組裝好的物件圖

Auto-Wiring:DI Container 的核心價值#

Auto-Wiring 是 DI Container 最重要的功能,也是它與 Pure DI 最大的區別:

  • 容器會自動分析每個類別的建構函式參數
  • 遞迴地解析每一個 Dependency,直到整個物件圖完成
  • 開發者只需告訴容器「哪些類型映射到哪些介面」,組裝邏輯完全自動化

Figure 12.2: Auto-Wiring 的簡化工作流程

Auto-Wiring 的價值在於:當你新增一個 Dependency 時,不需要手動修改 Composition Root 中的組裝程式碼——容器會自動處理。

Figure 12.3: Composition Root 向容器請求 HomeController,容器遞迴呼叫自身以解析依賴

三種配置方式#

DI Container 提供三種不同的配置途徑,各有優缺點:

1. Configuration Files(XML / JSON)#

  • 以外部設定檔定義型別映射
  • 優點:支援 Late Binding,不需重新編譯即可更換實作
  • 缺點:冗長、容易出錯、沒有編譯期檢查

2. Configuration as Code#

  • 以程式碼明確註冊型別映射
  • 優點:有編譯期檢查、IDE 支援重構
  • 缺點:需要手動逐一註冊
  • 這是最常見的配置方式
container.Register<IProductService, ProductService>();
container.Register<IProductRepository, SqlProductRepository>();

3. Auto-Registration(Convention over Configuration)#

  • 掃描 Assembly,依照慣例自動批次註冊
  • 例如:「所有實作 ICommandService 的類別,自動註冊為其介面」
  • 這是 DI Container 相對於 Pure DI 的最大優勢
container.RegisterAssemblyTypes(assembly)
    .Where(t => t.Name.EndsWith("Service"))
    .AsImplementedInterfaces();

Auto-Registration 是選擇 DI Container 而非 Pure DI 的主要理由。如果你的應用程式無法從 Auto-Registration 中獲益,使用 Pure DI 可能是更好的選擇。

Figure 12.5: DI Container 三種常見配置方式的明確性與綁定程度比較

DI Container vs Pure DI:如何選擇?#

DI Container 的成本#

  • 學習曲線——需要學習特定容器的 API 和配置方式
  • 第三方依賴——引入了額外的函式庫依賴
  • 執行期錯誤——配置錯誤只能在執行時被發現
  • 誤用風險——錯誤使用容器(如 Service Locator)可能比不用更糟

Pure DI 的優勢#

  • 編譯期錯誤——組裝邏輯就是普通程式碼,錯誤在編譯時發現
  • 零依賴——不需要任何第三方函式庫
  • 更簡單——沒有「神奇」的行為,所有邏輯一目了然

作者的建議#

場景建議
大型應用程式,大量相似的註冊邏輯使用 DI Container + Auto-Registration
中小型應用程式Pure DI 已足夠
團隊對 DI Container 不熟悉從 Pure DI 開始

Figure 12.6: Pure DI 因其簡單性而有價值,DI Container 則取決於使用方式

書中的核心觀點:「DI Container 是一個有用但可選的工具。」 它不是 DI 的必要條件,Pure DI 本身已是完全正當的做法。

簡易 DI Container 實作#

書中提供了一個簡化版的 DI Container 實作,展示 Auto-Wiring 的運作原理:

public object Resolve(Type type)
{
    // 查找是否有已註冊的映射
    Type concreteType = registrations.ContainsKey(type)
        ? registrations[type]
        : type;

    // 取得建構函式
    var ctor = concreteType.GetConstructors().Single();

    // 遞迴解析每個參數
    var parameters = ctor.GetParameters()
        .Select(p => Resolve(p.ParameterType))
        .ToArray();

    return ctor.Invoke(parameters);
}

這段程式碼清楚地展示了 Auto-Wiring 的核心邏輯:

  • 使用 Reflection 檢查建構函式參數
  • 遞迴呼叫 Resolve 解析每一個依賴
  • 最終組裝出完整的物件圖
關鍵結論

DI Container 的價值不在於取代 Pure DI,而在於當應用程式規模夠大時,Auto-Registration 能大幅減少重複的註冊程式碼。選擇 DI Container 與否,取決於 Auto-Registration 是否能為你的專案帶來實質幫助。