DI 容器 (DI Container)

★★★★Studied

FromDependency Injection

📚 From the Books

DI 容器 (DI Container)ConceptDI & IoC

一個能自動化組裝的函式庫(Spring、Dagger、Koin、Autofac…)。你只告訴它「哪個介面對到哪個實作」,它就靠 Auto-Wiring 遞迴解析建構子參數、把整張物件圖組好回傳。核心 API 極簡:resolve<T>()。但它是有用而可選的工具——不是 DI 的必要條件,Pure DI 本身已完全正當。

🧠 When to Use

TIP

訊號:你的 Composition Root 塞滿了幾百行「A 需要 B、B 需要 C」的手工 new,而且大量註冊長得都一樣(每個 XxxService 都對到 IXxxService)——這種重複,正是容器的 Auto-Registration 能吃掉的。

⚖️ Structure & Variants

Auto-Wiring —— 容器的核心價值
// 你只註冊映射,容器負責遞迴解析整張圖
container.register<ProductService, ProductServiceImpl>()
container.register<ProductRepository, SqlProductRepository>()

val service = container.resolve<ProductService>()  // 依賴自動被組好
  • 容器分析每個類別的建構子參數,遞迴解析每個依賴直到整張圖完成。
  • 好處:新增一個依賴時,通常不必手改 Composition Root 的組裝碼——容器自動處理。
三種配置方式(明確性 vs 綁定彈性的取捨)
  • 設定檔(XML/JSON):支援 late binding、免重編即可換實作;但冗長、易錯、無編譯期檢查
  • Configuration as Code:用程式碼逐一註冊(container.register<A, B>())。有編譯期檢查、IDE 可重構,是最常見的方式。
  • Auto-Registration(慣例優於配置):掃描套件、依慣例批次註冊(「所有 *Service 自動註冊為其介面」)。這是容器相對 Pure DI 的最大優勢
// Auto-Registration:一條規則取代幾十行註冊
container.registerAssemblyTypes(pkg)
    .where { it.name.endsWith("Service") }
    .asImplementedInterfaces()
容器本質就是一段 Auto-Wiring 迴圈
// 簡化版 resolve:反射拿建構子 → 遞迴解析參數 → 建立
fun resolve(type: KClass<*>): Any {
    val concrete = registrations[type] ?: type
    val ctor = concrete.constructors.single()
    val args = ctor.parameters.map { resolve(it.type.classifier as KClass<*>) }
    return ctor.call(*args.toTypedArray())
}
  • 看穿魔法:容器=用反射檢查建構子、遞迴 resolve 每個依賴、組出物件圖。它就是「自動化的工廠」。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading