介紹

依賴注入(Dependency Injection),落實了在類別的相依性之間達成控制反轉(IoC)的技術, 用於減少程式碼的耦合度,增強模組間的獨立性及可測試性。 .NET Core 推出以來,已內建DI功能,較早期如.Net Framework等 也可以透過第三方DI套件來使用。

什麼是Dependency Injection

我們都知道物件導向程式設計在建立一個物件時會透過new Class名稱()

假設在開發的時候用new的方式建立物件,會有什麼副作用嗎?

以下是一個簡單的範例

public class TaxCalculator
{
    public decimal CalculateTax(decimal amount)
    {
        return amount * 0.08M;  // 假設稅率是8%
    }
}

public class ShoppingCart
{
    private List<decimal> _prices;
    private TaxCalculator _taxCalculator;

    public ShoppingCart()
    {
        _prices = new List<decimal>();
        _taxCalculator = new TaxCalculator();  // 直接透過new()創建
    }

    public void AddPrice(decimal price)
    {
        _prices.Add(price);
    }

    public decimal CalculateTotal()
    {
        decimal subtotal = _prices.Sum();
        return subtotal + _taxCalculator.CalculateTax(subtotal);
    }
}

可能的問題如下

  1. 耦合性高:ShoppingCart 依賴於具體的 TaxCalculator,這使得它難以適應 TaxCalculator的變化。
  2. 難以測試:由於 TaxCalculator的new出來的實例被放到 ShoppingCart 中,這使得在單元測試 ShoppingCart 時,難以對 TaxCalculator 進行模擬。
  3. 低靈活性:如果未來需要更換不同的稅率或配置,需要修改 ShoppingCart 的內部實現。

那我們能怎麼做呢? 這時候DI就派上用場啦~ 以下是修改後的範例

public interface ITaxCalculator
{
    decimal CalculateTax(decimal amount);
}

public class TaxCalculator : ITaxCalculator
{
    public decimal CalculateTax(decimal amount)
    {
        return amount * 0.08M;
    }
}

public class ShoppingCart
{
    private List<decimal> _prices;
    private ITaxCalculator _taxCalculator;

    // 通過建構子注入依賴
    public ShoppingCart(ITaxCalculator taxCalculator)
    {
        _prices = new List<decimal>();
        _taxCalculator = taxCalculator;
    }

    public void AddPrice(decimal price)
    {
        _prices.Add(price);
    }

    public decimal CalculateTotal()
    {
        decimal subtotal = _prices.Sum();
        return subtotal + _taxCalculator.CalculateTax(subtotal);
    }
}

以上透過建構子注入所需介面的方式來使用可以解決高耦合的相關問題 ShoppingCart 類不再自行創建 TaxCalculator 的實例。相反,它通過構造函數接收一個 ITaxCalculator 介面的實現,這種方式讓我們可以靈活地替換不同的稅率計算邏輯。 此外,這也使得在進行單元測試時,我們能夠輕鬆地注入一個假的稅率計算器。這樣的依賴注入方法大幅提升了程式碼的可測試性和可維護性。

.Net內建的DI怎麼用?

設定服務

在 ConfigureServices 方法中註冊所有的依賴:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ITaxCalculator, TaxCalculator>();
    // services.AddSingleton<ILoggerManager, LoggerManager>();
}

上述設置很簡單吧 你有發現Scoped跟Singleton嗎? 其實DI註冊也是有其生命週期的! 讓我們繼續看下去~

DI的生命週期

Singleton:每個Process(DI容器)僅一個實例

Scoped:每個HttpRequest會請求一個實例

Transient:每次注入時都會建立新的實例

結語

理解和利用 .NET DI 容器不僅可以提升你的開發效率,還能幫助你構建更加健壯和可維護的應用。隨著對依賴注入深入了解,你將能夠更好地設計和實施複雜的業務應用。