2026-06-20 C# DI 的語法構造是什麼?
2026-06-20 C# DI 的語法構造是什麼?
我:
DI 的語法構造是什麼?
GPT:
在 C# 中,依賴注入(Dependency Injection, DI)是一種設計模式,通常透過建構子注入、屬性注入或方法注入等方式來實現。依賴注入的主要目的是讓程式更具彈性和可測試性,因為具體實現可以在外部動態替換。
1. 建構子注入 (Constructor Injection)
建構子注入是最常見的依賴注入方式。主要是在類的建構子中傳入依賴的物件,並將其儲存在類的私有欄位中。
1 | // 1. 定義接口 |
2. 屬性注入 (Property Injection)
屬性注入是通過公共屬性將依賴注入進去。在某些情況下,如果依賴不是在建構子時就必須使用,這種方式可能會更靈活。
1 | public class PaymentProcessor |
3. 方法注入 (Method Injection)
在方法注入中,依賴作為參數傳入到方法中,這通常適用於臨時需要的依賴,不一定要成為類的屬性或欄位。
1 | public class PaymentProcessor |
4. 使用內建的依賴注入容器
在 ASP.NET Core 等框架中,內建了 DI 容器,可以更方便地管理和注入依賴。在 Startup.cs 中配置服務:
1 | public void ConfigureServices(IServiceCollection services) |
在使用時,透過依賴注入容器自動解析並注入:
1 | public class SomeController : ControllerBase |
總結
- 建構子注入:適合必須提供的依賴。
- 屬性注入:適合可選的依賴。
- 方法注入:適合臨時需要的依賴。
- 使用DI 容器能夠自動解析依賴,簡化管理。
通過這些模式和方法,依賴注入讓程式碼更加模組化,增強了可讀性、測試性和擴展性。
我:
為什麼要用DI 注入? 它有演進過程嗎?
GPT:
依賴注入 (Dependency Injection, DI) 是一種用來實現鬆耦合設計的設計模式,目的是改善系統的彈性、可維護性和測試性。DI 的出現源於面向對象設計中的依賴倒置原則(Dependency Inversion Principle, DIP)以及控制反轉(Inversion of Control, IoC)的理念。這個過程經歷了幾個階段的演進。
DI 的演進過程
硬編碼依賴 (Tightly Coupled Code)
最早期,物件之間的依賴是直接硬編碼的。例如,一個PaymentProcessor可能直接依賴於CreditCardPayment類別:1
2
3
4
5
6
7
8
9public class PaymentProcessor
{
private CreditCardPayment _paymentMethod = new CreditCardPayment();
public void CompletePurchase(decimal amount)
{
_paymentMethod.ProcessPayment(amount);
}
}這種硬編碼方式帶來很大的限制。每次要更改支付方式,都需要修改
PaymentProcessor類別的程式碼,導致系統的擴展性和維護性變差。使用接口進行抽象 (Programming to an Interface)
為了實現鬆耦合,我們可以引入接口。接口定義了支付方法的抽象,並允許不同的支付方式去實現該接口,但仍然有個缺點:我們還是必須在類別內部實例化具體的支付方式。1
2
3
4
5
6
7
8
9public class PaymentProcessor
{
private IPaymentMethod _paymentMethod = new CreditCardPayment();
public void CompletePurchase(decimal amount)
{
_paymentMethod.ProcessPayment(amount);
}
}依賴注入的引入 (Dependency Injection)
DI 將具體實例的創建移到外部,讓PaymentProcessor類別不再直接實例化支付方式,而是由外部在初始化時提供具體的依賴,從而達到完全的鬆耦合設計。現在,只需改變外部傳入的依賴即可變更支付方式,而無需修改PaymentProcessor類別。1
2
3
4
5
6
7
8
9
10
11
12
13
14public class PaymentProcessor
{
private readonly IPaymentMethod _paymentMethod;
public PaymentProcessor(IPaymentMethod paymentMethod)
{
_paymentMethod = paymentMethod;
}
public void CompletePurchase(decimal amount)
{
_paymentMethod.ProcessPayment(amount);
}
}使用 DI,程式碼變得更具彈性,因為可以在建構
PaymentProcessor時提供不同的支付方式,例如CreditCardPayment或PaypalPayment。DI 容器和自動化依賴管理
在大型應用程式中,手動管理依賴注入可能變得複雜,因此 DI 容器應運而生。DI 容器可以自動解析和管理依賴關係,從而簡化了注入過程。框架如 ASP.NET Core 提供了內建的 DI 容器,允許透過配置的方式在整個應用中管理依賴。
DI 的優勢
- 減少耦合性:物件之間的依賴關係變得靈活,不再直接依賴具體實現,而是依賴於抽象接口。
- 提高可測試性:通過 DI,可以輕易替換依賴的具體實現,例如在單元測試中注入模擬 (mock) 物件。
- 增強可擴展性:增加新功能或替換某個依賴時,不需要更改使用這些依賴的類別。
- **控制反轉 (IoC)**:透過 DI,對象的依賴由容器管理,不再由物件自己控制,這是 IoC 原則的具體應用。
總結
DI 的引入是對系統設計演進過程中的一次重大改進,使系統的各個組件可以更加靈活地替換、測試和擴展。在應用 DI 模式時,建構一個高效的依賴管理系統(如 DI 容器)可以大幅簡化代碼,減少耦合,並提升整體代碼品質。
我:
妳說的 1. 2. 3. 演進過程, 有點難懂, 可以用具體的程式碼說明, 每一個演進遇到的困難嗎?
GPT:
當然可以,我們來看一個具體的範例,從最初的硬編碼依賴,到使用接口,再到依賴注入的改進,每一步都會展示程式碼如何演進,以及為什麼每個演進步驟更有利於系統的設計。
1. 硬編碼依賴 (Tightly Coupled Code)
在最早期的設計中,我們會直接在類別中實例化依賴對象,比如說在 PaymentProcessor 中直接依賴 CreditCardPayment 類別:
1 | public class CreditCardPayment |
問題
- 如果未來需要支持其他支付方式(例如 PayPal 或銀行轉帳),我們需要改動
PaymentProcessor類別,新增或替換具體的支付類別。 - 系統的可擴展性差:每次需求變更都需要直接修改程式碼。
- 測試難度增加:無法模擬
CreditCardPayment來進行單元測試。
2. 引入接口 (Programming to an Interface)
我們可以將支付方式抽象成一個接口 IPaymentMethod,讓不同支付方式各自實現這個接口。這樣,PaymentProcessor 類別可以依賴於 IPaymentMethod 而非具體的 CreditCardPayment。
1 | public interface IPaymentMethod |
優點和問題
- 可擴展性提升:增加支付方式時,只需新增實現
IPaymentMethod的類別,PaymentProcessor不需要改動。 - 耦合性降低:
PaymentProcessor不再依賴於具體的支付方式。 - 問題仍然存在:
PaymentProcessor依然自己決定具體使用哪一個支付方式(仍然硬編碼了new CreditCardPayment()),導致擴展性受限。如果需求更變,仍需修改PaymentProcessor類別的程式碼。
3. 依賴注入 (Dependency Injection)
這一步,我們通過依賴注入來進一步改善設計,將 PaymentProcessor 的支付方式作為外部傳入的依賴,從而達到更高的靈活性。
1 | public interface IPaymentMethod |
現在,使用 PaymentProcessor 時,可以選擇傳入不同的支付方式:
1 | IPaymentMethod paymentMethod = new CreditCardPayment(); |
優點
- 完全鬆耦合:
PaymentProcessor不再負責創建具體支付方式。它的依賴由外部提供,使得系統更具彈性。 - 便於測試:可以在測試中傳入一個模擬
IPaymentMethod,來測試PaymentProcessor的功能,而不需要實際的支付方式。 - 擴展性更高:增加或更改支付方式時,無需修改
PaymentProcessor的內部實現。
DI 總結
依賴注入將類別的依賴關係從內部創建轉移到外部提供,使系統更易擴展和維護。在大型應用中,依賴注入可以使用 DI 容器自動管理依賴,進一步簡化設計流程。