本章介紹四種常見的 DI 反模式。這些做法表面上看似合理甚至有用,但實際上會破壞鬆耦合的目標,應當辨識並避免。
Control Freak#
Control Freak 是最常見的 DI 反模式——在 Composition Root 以外的地方直接依賴 Volatile Dependency。
常見表現形式:
- 直接
new出 Volatile Dependency 的具體實作 - Factory 內部硬編碼具體型別
- 提供多載建構子 (overloaded constructor),在無參數版本中建立預設依賴
// Control Freak: 在業務邏輯中直接建立具體依賴
public class ProductService
{
private readonly SqlProductRepository repository;
public ProductService()
{
this.repository = new SqlProductRepository(); // 緊耦合
}
}
new關鍵字本身不是問題。對 Stable Dependencies(如string、List<T>、DTO 等)使用new完全正常。只有在 Composition Root 以外對 Volatile Dependencies 使用new才是 Code Smell。

Figure 5.2: 靜態 ProductRepositoryFactory 造成 Domain 與 Data Access 層之間的循環依賴
修正方式:將依賴透過 Constructor Injection 注入,並將組裝邏輯移至 Composition Root。
Service Locator#
Service Locator 是最危險的反模式,因為它偽裝成一種解決方案。
核心問題:
- 隱藏依賴——類別的 constructor 不再誠實宣告它需要什麼
- 將編譯期錯誤推遲到執行期——缺少註冊只會在 runtime 爆炸
- 將 Container 依賴拖進整個程式碼庫——每個類別都必須知道 Service Locator 的存在
// Service Locator: 主動向 Container 要依賴
public class ProductService
{
public void GetFeaturedProducts()
{
var repository = Locator.GetService<IProductRepository>();
// ...
}
}
Figure 5.3: HomeController 與 Service Locator 之間的互動
DI Container 在 Composition Root 以外被使用,就是 Service Locator。 這是判斷的核心準則。即使是「官方的」DI Container,錯誤的使用方式仍然是反模式。

Figure 5.5: Visual Studio IntelliSense 只能告訴我們 ProductService 有無參數建構子,其依賴完全不可見
修正方式:以 Constructor Injection 取代,讓依賴在 constructor 中明確宣告。
Ambient Context#
Ambient Context 透過 static accessor 提供單一 Dependency,讓整個應用程式都能「環境式地」存取。
常見範例:
TimeProvider.Current、DateTime.Now的靜態包裝- 靜態 Logger(如
Log.Information(...)) Thread.CurrentPrincipal
問題分析:
- 隱藏依賴——與 Service Locator 相同,無法從 constructor 看出依賴關係
- 難以測試——必須操作全域狀態,平行測試會互相干擾
- 違反 SRP——任何類別都能輕易存取,鼓勵職責擴散
- 共享可變狀態——多執行緒環境下容易產生競態條件 (race condition)
修正方式:將原本透過 static accessor 取得的依賴改為 Constructor Injection,讓依賴關係顯式化。
Constrained Construction#
Constrained Construction 發生在程式碼假設所有實作都必須具備特定的 constructor 簽章(通常是無參數建構子),以便透過反射 (reflection) 進行 late binding。
// Constrained Construction: 假設所有 plugin 都有無參數建構子
Type type = Type.GetType(typeName);
var plugin = (IPlugin)Activator.CreateInstance(type);問題:
- 限制了實作類別的彈性——無法透過 constructor 注入自身的依賴
- 阻礙了正規的 DI 運作方式
- 實質上是一種隱式的介面合約,但編譯器無法檢查
修正方式:使用 DI Container 或在 Composition Root 中以 Constructor Injection 的方式建立物件,不再限制 constructor 簽章。
反模式辨識摘要#
四種反模式的快速比較
| 反模式 | 核心問題 | 典型信號 | 修正方向 |
|---|---|---|---|
| Control Freak | Composition Root 外建立 Volatile Dependency | new ConcreteType() 散布各處 | Constructor Injection |
| Service Locator | 主動向 Container 索取依賴 | container.GetService<T>() 出現在業務程式碼 | Constructor Injection |
| Ambient Context | 透過 static accessor 存取依賴 | SomeContext.Current、靜態 Logger | Constructor Injection |
| Constrained Construction | 假設固定 constructor 簽章 | Activator.CreateInstance(type) | DI Container / Composition Root |