一個物件需要的協作對象,不自己
new,而是「從外面交給它」(建構子、setter 或參數)。控制反轉(IoC)是觀念,依賴注入是把這觀念落地的最常見手法。
🧠 When to Use
TIP
訊號:你想替某個類別寫單元測試,卻發現它在內部 new 了資料庫連線 / HTTP client / 時鐘,根本換不掉——這就是該注入的地方。
- 物件依賴外部資源或會變的實作(DB、網路、檔案、第三方服務、時間)。
- 想在測試時換成假物件(mock / fake / stub),達成隔離測試。
- 想讓「組裝整個系統」的決定集中在一處(composition root),其餘程式只依賴抽象。
⚖️ Structure & Variants
三種注入方式
interface Clock { fun now(): Long }
// 1) 建構子注入(首選:依賴顯式、物件一建立就完整)
class Report(private val clock: Clock) {
fun stamp() = "generated at ${clock.now()}"
}
// 2) Setter / property 注入(依賴可選、或要可重設時)
class Cache { var clock: Clock? = null }
// 3) 方法 / 參數注入(只有這個方法需要時)
fun render(clock: Clock) = clock.now()- 建構子注入是預設:物件不可能處於「半組好」狀態,依賴一眼可見。
DIP、IoC、DI 三個詞的關係
- DIP(依賴反轉原則):高層與低層都依賴抽象,別讓高層直接依賴低層細節。是「為什麼」。
- IoC(控制反轉):把「流程 / 建立」的控制權交給外部框架或組裝者。是「方向」。
- DI(依賴注入):把依賴從外面傳進來的具體做法。是「怎麼做」。
手動 DI vs DI 容器
- 手動:在
main/ composition root 親手new並串接。小專案最清楚、零魔法。 - 容器(Spring、Dagger、Koin…):宣告依賴關係,容器負責建立與串接。省樣板,但有學習與除錯成本。
- 容器是「自動化的工廠」;它與 Factory Method 解決的是同一類「誰負責建立」的問題。
⚠️ Misuse & Anti-patterns
- Service Locator 當 DI:物件自己跟一個全域容器「要」依賴(
locator.get<Clock>()),依賴又被藏起來了——表面注入、實則隱性耦合。 - 過度注入:建構子注進七八個依賴,通常是這個類別責任太多(違反 SRP)的味道,不是該再加參數。
- 到處 new 而非注入:在類別內部直接
new具體資源,等於把它焊死,測試無從替換。 - 容器當銀彈:小程式硬上容器,徒增魔法與啟動複雜度;手動 DI 反而更清楚。
🔑 Takeaways
- 核心動作:依賴從外面交進來,物件只依賴抽象,建立的決定集中在 composition root。
- 預設用建構子注入;依賴顯式、物件一建立即完整、最好測。
- DIP(為什麼)→ IoC(方向)→ DI(做法);容器是自動化工廠,但別當銀彈。
- Service Locator 把依賴藏起來,是偽 DI;建構子塞太多通常是責任過重的警訊。
No notes yet — jot your takeaways or Q&A here.