DI 依賴注入:為什麼不直接 new?
先看問題:直接 new 有什麼不好?
public class OrderService {
private readonly SqlOrderRepository _repo = new SqlOrderRepository(); // ← 直接 new
private readonly SmtpEmailService _email = new SmtpEmailService(); // ← 直接 new
}
表面上可以動,但隱藏了三個嚴重問題:
問題 1:無法替換(緊耦合)
OrderService 直接依賴 SqlOrderRepository
→ 換成 MongoOrderRepository?要改 OrderService 的程式碼
→ 違反「開閉原則」(對擴展開放,對修改封閉)
問題 2:無法測試
// 想測試 OrderService 的邏輯,但它直接 new 了真的資料庫連線
// 你不能注入一個假的 Repository → 無法做單元測試
[Test]
public void CreateOrder_ShouldSendEmail() {
var service = new OrderService(); // ← 會真的連資料庫!測試環境沒有 DB 就爆了
}
問題 3:無法管理生命週期
new SqlOrderRepository() → 每次都建新連線
→ 連線池爆掉
→ 你無法控制「整個 Request 共用一個」或「全域只有一個」
DI 怎麼解決?
核心概念:把依賴「注入」進來,不要自己 new
// ❌ 直接 new(控制權在 OrderService 內部)
public class OrderService {
private readonly SqlOrderRepository _repo = new SqlOrderRepository();
}
// ✅ 依賴注入(控制權在外部)
public class OrderService {
private readonly IOrderRepository _repo;
public OrderService(IOrderRepository repo) { // ← 從外部注入
_repo = repo;
}
}
控制反轉(IoC):「我不自己建立依賴,我要求別人給我。」
三種生命週期
builder.Services.AddSingleton<ICacheService, RedisCacheService>(); // 全域一個
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>(); // 每個 Request 一個
builder.Services.AddTransient<IEmailService, SmtpEmailService>(); // 每次注入都 new 一個
| 生命週期 | 何時建立 | 何時銷毀 | 適合場景 |
|---|---|---|---|
| Singleton | 應用啟動時 | 應用關閉時 | 快取、設定、HttpClient |
| Scoped | 每個 HTTP Request | Request 結束時 | DbContext、Repository |
| Transient | 每次注入時 | 超出作用域時 | 輕量無狀態服務 |
常見陷阱:Captive Dependency
Singleton 注入 Scoped → ❌ Scoped 物件被 Singleton 抓住,永遠不會釋放
→ DbContext 被快取,資料永遠是舊的
→ ASP.NET Core 預設會拋出例外提醒你
不用 DI 的替代方案
| 方式 | 做法 | 缺點 |
|---|---|---|
| 直接 new | new SqlRepo() |
緊耦合、不可測試 |
| Service Locator | ServiceLocator.Get<IRepo>() |
隱藏依賴、難以追蹤 |
| Factory Pattern | RepoFactory.Create() |
比 DI 囉唆,但有時合理 |
| 靜態方法 | OrderRepo.GetAll() |
無法 mock、全域狀態 |
DI 不是唯一選擇,但在 Web 應用中是最適合的,因為 HTTP Request 的生命週期天然適合 Scoped。
什麼時候「不需要」DI?
- 工具類(
Math.Max()、string.Format())→ 靜態就好 - 簡單的值物件(DTO、Record)→ 直接 new
- 一次性的 Console App → 過度設計
- 沒有替換需求、不需要測試的小程式
原則:如果一個類別有「行為」且你可能想替換或測試它,就用 DI。
面試角度
Q: 為什麼要用 DI?
A: 解耦合、可測試、生命週期管理。讓類別不需要知道依賴的具體實作。
Q: Scoped 和 Singleton 差在哪?
A: Scoped 是每個 Request 一個實例(適合 DbContext),Singleton 全域共用(適合快取)。
Q: 什麼是 IoC?
A: 控制反轉。傳統是類別自己 new 依賴,IoC 是由外部(容器)提供依賴。DI 是 IoC 的一種實現方式。