本章探討三種與 DI 相關的程式碼異味。關鍵觀念是:這些異味通常不是 DI 造成的問題,而是 DI(特別是 Constructor Injection)讓底層的設計缺陷變得可見。正確的應對方式是改善設計,而非繞過 DI。
Constructor Over-injection#
當一個類別的 constructor 參數超過四個,通常代表這個類別承擔了太多職責,違反了 Single Responsibility Principle (SRP)。
Constructor Injection 只是讓 SRP 違規「浮出水面」。不要怪罪 DI——問題在於類別設計本身。

Figure 6.1: OrderService 有五個直接依賴,暗示違反 SRP
錯誤的修正方式#
將部分參數改為 Property Injection——這只是隱藏症狀,依賴關係並沒有減少,反而失去了 Constructor Injection 提供的編譯期保障。
正確的修正方式#
Facade Services#
建立新的 Abstraction,將多個相關的 Dependencies 封裝在一起,同時封裝它們的互動行為。
// 重構前:OrderService 有太多依賴
public OrderService(
IOrderRepository orderRepo,
IInventoryService inventory,
IPaymentService payment,
INotificationService notification,
IShippingService shipping) { ... }
// 重構後:引入 Facade Service
public OrderService(
IOrderRepository orderRepo,
IOrderFulfillmentService fulfillment) { ... }
// OrderFulfillmentService 封裝了 inventory + payment + shipping 的互動Facade Service 不是簡單的 Parameter Object——它必須封裝行為,不只是打包參數。如果新的 Abstraction 只是把依賴收集在一起而沒有自己的邏輯,那就是 Header Interface 反模式。

Figure 6.2: 兩個 OrderService 依賴聚合在 Facade Service 背後
Domain Events#
以 Domain Events 捕捉觸發狀態變更的動作,將核心邏輯與 Cross-Cutting Concerns 解耦。
- 核心操作只負責發布事件
- 事件處理器 (Event Handlers) 各自負責通知、日誌、稽核等關注點
- 減少核心類別對多個 Cross-Cutting 服務的直接依賴

Figure 6.3: 重構後的最終 OrderService 依賴結構
Abuse of Abstract Factories#
Abstract Factory 在 DI 中經常被過度使用,但大多數情況下是不必要的。
常見的濫用場景#
生命週期不匹配問題:長壽命的消費者需要短壽命的依賴,因此引入 Factory 來「按需建立」。
// 常見但通常不必要的 Factory 用法
public class OrderController
{
private readonly IOrderServiceFactory factory;
public void PlaceOrder(OrderDto dto)
{
var service = factory.Create(); // 每次建立新實例
service.Process(dto);
}
}這類問題通常可以透過正確配置 DI Container 的 Lifestyle (Scoped、Transient 等) 來解決,不需要 Factory。
執行期資料選擇:根據 runtime 條件選擇不同實作——更好的做法是使用 Composite Pattern 或 Strategy Pattern。
判斷準則:無參數的
Create()方法幾乎總是 Code Smell。如果 Factory 不需要任何執行期參數就能建立物件,那這個 Factory 很可能是多餘的。
合理使用的條件#
只有在需要傳入真正的執行期資料作為參數來建立物件時,Abstract Factory 才是合理的選擇。
Cyclic Dependencies#
Cyclic Dependencies(循環依賴)通常是 SRP 違規的信號——兩個或多個類別互相依賴,代表它們的職責邊界劃分不當。

Figure 6.12: Chicken 與 Egg 之間的依賴循環
解決策略#
依優先順序:
- 拆分類別——找出混合在一起的職責,將其分離為獨立的類別,打破循環
- 引入中介者 (Mediator) 或 Domain Events——讓雙方不直接依賴彼此,而是透過中介機制溝通
- Property Injection——作為最後手段,用於打破其中一個方向的依賴
Property Injection 只是技術上的解法,並未解決根本的設計問題。應優先考慮前兩種策略。

Figure 6.14: 將 IUserRepository 分離為兩個介面以打破依賴循環
程式碼異味處理摘要#
三種 Code Smells 的識別與修正
| Code Smell | 識別信號 | 根本原因 | 正確修正 |
|---|---|---|---|
| Constructor Over-injection | Constructor 參數 > 4 | 違反 SRP | Facade Services / Domain Events |
| Abuse of Abstract Factories | 無參數 Create() 方法 | 不必要的間接層 | 正確配置 Lifestyle / Composite Pattern |
| Cyclic Dependencies | A 依賴 B 且 B 依賴 A | SRP 違規 | 拆分類別 / Mediator / Domain Events |