Mise en Place:最後一刻才組裝#

本章以法式烹飪的 Mise en place(就位準備)作為比喻:廚師在開始烹調之前,先將所有食材清洗、切好、備妥,最後才進行組合與烹調。軟體開發中的 Object Composition 也應如此——先開發好各個獨立元件,在最後一刻才組裝成完整的物件圖 (Object Graph)。

這個「最後一刻」就是 Composition Root——應用程式啟動時負責建構整個物件圖的唯一位置。

Figure 7.2: Composition Root 組裝應用程式的所有獨立模組

Composition Root 的核心原則#

  • Composition Root 應該盡可能靠近應用程式的進入點 (Entry Point)
  • 整個應用程式中只應有一個 Composition Root
  • 它是唯一允許直接建立 Dependencies 的地方
  • 所有元件的組裝邏輯集中在此,其餘程式碼只透過 Abstraction 互動

Composition Root 不是一個特定的類別或框架功能,而是一個架構概念——它是你決定「在哪裡組裝物件圖」的位置。

Console 應用程式#

Console 應用程式是最簡單的情境,Program.Main 方法就是天然的 Composition Root。

static void Main(string[] args)
{
    // Composition Root: 在這裡組裝整個物件圖
    var repository = new SqlProductRepository(connectionString);
    var service = new ProductService(repository);
    var command = new UpdateCurrencyCommand(service);

    command.Execute();
}
  • 所有 new 關鍵字集中在 Main 方法中
  • 組裝完成後呼叫頂層物件的方法,啟動整個應用流程
  • UpdateCurrency 範例清楚展示了這個模式:建立 Dependencies → 組裝 → 執行

Figure 7.3: UpdateCurrency 應用程式的元件組裝結構

UWP 應用程式#

UWP (Universal Windows Platform) 應用程式的框架生命週期較為複雜:

  • 框架控制了頁面 (Page) 的建立過程,無法直接透過建構函式注入
  • App 類別成為 Composition Root,利用 OnLaunched 等生命週期事件進行組裝
  • 需要透過 Navigation 機制將組裝好的物件傳遞給各頁面

UWP 的挑戰在於框架會自行建立某些物件(如 Page),開發者必須找到框架提供的「接縫 (Seam)」來插入自己的組裝邏輯。

ASP.NET Core MVC#

ASP.NET Core MVC 是書中最詳細的範例,展示了在 Web 應用程式中實踐 Pure DI 的方法。

Figure 7.9: ASP.NET Core MVC 請求管線

自訂 IControllerActivator#

ASP.NET Core 透過 IControllerActivator 介面來建立 Controller 實例。實作自訂的 Activator 就能完全控制 Controller 的建構過程:

public class CompositionRoot : IControllerActivator
{
    public object Create(ControllerContext context)
    {
        Type type = context.ActionDescriptor
            .ControllerTypeInfo.AsType();

        if (type == typeof(HomeController))
            return new HomeController(
                new ProductService(
                    new SqlProductRepository(connectionString)));

        throw new InvalidOperationException($"Unknown controller: {type}");
    }

    public void Release(ControllerContext context, object controller)
    {
        // 處理 Disposable 物件
    }
}

Figure 7.10: 範例應用程式中的兩個 Controller 及其依賴

Composition Root 位於 Startup#

  • Startup.ConfigureServices 中註冊自訂的 IControllerActivator
  • 自訂 Middleware 同樣可以使用 Constructor Injection
  • Composition Root 集中在 Startup 類別及相關的 Activator 中

Constructor Injection 與 Middleware#

自訂 Middleware 是 ASP.NET Core 的重要擴充點。透過 Pure DI,Middleware 的 Dependencies 也在 Composition Root 中組裝,確保整個請求管線都遵循 DI 原則。

關鍵洞察#

本章最重要的觀點是:Object Composition 本身並不困難,困難來自於應用程式框架

  • 每個框架對物件的建立流程有不同的控制方式
  • 開發者需要理解每個框架提供的接縫 (Seam)——即框架允許你插入自訂邏輯的擴充點
  • 一旦找到正確的接縫,Pure DI 的組裝方式在所有框架中都是一致的

面對不熟悉的框架時,先問自己:「這個框架在哪裡建立我的物件?它提供了什麼擴充點讓我介入這個過程?」找到這個接縫,就找到了 Composition Root 的位置。

不同框架的 Composition Root 位置
框架類型Composition Root 位置接縫 (Seam)
ConsoleProgram.Main直接控制進入點
UWPApp.OnLaunchedApplication 生命週期事件
ASP.NET Core MVCStartup + 自訂 IControllerActivatorIControllerActivator 介面