DI 反模式 (DI Anti-patterns)

★★★★Studied

FromDependency InjectionAgile Principles, Patterns, and Practices

📚 From the Books

DI 反模式 (DI Anti-patterns)ConceptDI & IoC

四種看似合理、實則破壞鬆耦合的 DI 反模式:Control FreakService LocatorAmbient ContextConstrained Construction。它們的共同病灶是同一個——把依賴藏起來,讓建構子不再誠實宣告「我需要什麼」,於是編譯期錯誤被推遲到執行期。

🧠 When to Use

TIP

訊號:一個類別的建構子空空如也,你卻在方法內部看到它「變出」了資料庫、時鐘、logger——依賴被藏在 newLocator.get() 或某個 .Current 靜態存取後面。這頁教你辨識這四種藏法。

⚖️ Structure & Variants

Control Freak —— 在 Composition Root 以外自己 new 會變的依賴
// 反模式:業務類別自己建立具體依賴,焊死了實作
class ProductService {
    private val repository = SqlProductRepository()   // 緊耦合,測試換不掉
}
  • 最常見的一種:直接 new、工廠內硬編具體型別、或多載建構子在無參版本裡塞預設依賴。
  • new 本身不是罪:對 stable 依賴(StringList、DTO)new 完全正常;只有在 Composition Root 外對會變的依賴(DB、網路、時鐘)new 才是臭味。
  • 修正:改建構子注入,把組裝挪到 Composition Root
Service Locator —— 最危險,因為它偽裝成解法
// 反模式:主動向全域容器「要」依賴
class ProductService {
    fun featured() {
        val repo = Locator.getService<ProductRepository>()  // 依賴被藏起來了
    }
}
  • 三宗罪:隱藏依賴(建構子不再誠實)、編譯期錯誤推遲到執行期(漏註冊要 runtime 才爆)、把容器依賴拖進整個 codebase(人人都得認得 Locator)。
  • 書中判準一針見血:DI 容器在 Composition Root 以外被使用,就是 Service Locator。 即使是「官方」容器,用錯地方仍是反模式。
  • 修正:改建構子注入,讓依賴在建構子明白宣告。
Ambient Context —— 用 static accessor 提供環境式依賴
  • 典型:DateTime.Now、靜態 Log.info(...)Thread.CurrentPrincipal 這種「隨手可取」的全域存取。
  • 問題和 Service Locator 同源:隱藏依賴難測(得操作全域狀態,平行測試互相干擾)、違反 SRP(人人可取、鼓勵職責擴散)、共享可變狀態(多執行緒競態)。
  • 修正:把靜態存取換成建構子注入(例如注入 Clock 而非直接讀系統時間)。
Constrained Construction —— 假設所有實作都有固定建構子簽章
// 反模式:假設 plugin 都有無參數建構子,好用反射晚綁
val type = Class.forName(typeName)
val plugin = type.getDeclaredConstructor().newInstance() as Plugin
  • 為了用反射做 late binding,硬性要求實作類別具備特定(通常是無參)建構子——於是這些實作自己沒辦法再注入依賴
  • 這是一種隱式介面合約,編譯器無法檢查。
  • 修正:改用 DI 容器或在 Composition Root 以建構子注入建立,不再限制簽章。

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

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

📖 Further Reading

🔗 Dive Deeper