Alva

Alva

programmer
github
telegram
email

dotnet 中的依賴注入-02.入門示例

本文示例代碼,均採用 .NET 6,具體的代碼可以在這個倉庫 Articles.DI 中獲取。

在 .NET 中的依賴關係注入是一等公民,官方框架了提供配置、日誌記錄和選項等模式。

依賴注入(DI)通過下面的方式,解決了前面的這些問題:

  • 使用介面或基類,將依賴關係實現抽象化;
  • 使用統一容器註冊服務;
  • 將服務注入到使用它的類中,由框架負責服務的實例化,且由框架管理它的聲明週期;

我們用一個示例,來展示如何在 .NET 中,使用依賴注入。下面這個示例是一個後臺服務Worker,其依賴MessageWriter,每隔 1s,調用 MessageWriter.Write 輸出一行簡單的日誌。

// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService2
// 定義 host 服務
IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
   .Build();

await host.RunAsync();  // 啟動

// 具體的後臺服務類
public class Worker : BackgroundService
{
    // 硬編碼依賴,需要自行初始化成員變量
    private readonly MessageWriter _messageWriter = new();

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1000, stoppingToken);
        }
    }
}

public class MessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}

使用依賴注入#

前面的代碼中,Worker類顯式依賴MessageWriter類,且在自己的代碼塊中,自己實例化了對應實例。

隨著項目的複雜度增加,Worker類包含多個不同變量,那麼都要Worker類自行實例化,而且若是這些變量又依賴其他服務,那麼光是初始化的代碼就會非常的繁雜。並且後續不使用MessageWriter類,想換一種實現,那麼也要修改代碼,兩個類之間的耦合非常緊密。

為了解開二者之間的耦合,我們根據下面的幾個步驟,使用依賴注入,做到控制反轉:

  1. MessageWriter類的行為抽象出一個介面IMessageWriter
  2. 在容器中註冊服務,將IMessageWriter的實現註冊為MessageWriter
  3. _messageWriter的類型修改為介面IMessageWriterWorker類只依賴介面;
    4.Worker類在構造函數中,聲明需要的類型變量,由容器進行注入;
IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
        {
            services.AddHostedService<Worker>();
            // 2. 註冊服務
            services.AddSingleton<IMessageWriter, MessageWriter>();
        })
    .Build();

await host.RunAsync();

// 1. 抽象介面
public interface IMessageWriter
{
    void Write(string message);
}

public class MessageWriter : IMessageWriter
{
    public void Write(string message)
    {
        Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
    }
}


public class Worker : BackgroundService
{
    // 3. 鬆耦合,只依賴介面,不依賴具體實現。Worker 類不負責初始化成員
    private readonly IMessageWriter _messageWriter;


    // 4. 從構造函數傳遞注入具體的實例,將其賦值給 _messageWriter 成員
    public Worker(IMessageWriter messageWriter)
    {
        _messageWriter = messageWriter;
    }


    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
            await Task.Delay(1000, stoppingToken);
        }
    }
}

上述代碼中,Worker類和MessageWriter類之間並沒有直接聯繫,前者擁有一個IMessageWriter類型的成員,容器服務會通過構造函數注入一個實例進來,Worker類可以直接使用,而無需知道如何構造一個該類型的實例。後者MessageWriter是一個實現IMessageWriter介面的類,它只需要根據介面要求,實現對應方法,不用去管其他人員會如何使用它,而在容器註冊服務時,將MessageWriter註冊為IMessageWriter的實現,那麼在有服務依賴於IMessageWriter時,容器會自動生成一個MessageWriter實例注入進去。

依賴注入將兩個類解耦,後續如果想要更換IMessageWriter的具體實現,可以直接添加一個新的實現類,然後修改註冊即可,可以參考下面的代碼:

public class LoggingMessageWriter : IMessageWriter
{
    private readonly ILogger<LoggingMessageWriter> _logger;


    public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger)
    {
        _logger = logger;
    }


    public void Write(string message)
    {
        _logger.LogInformation(message);
    }
}


// 註冊服務: ConfigureServices 代碼塊
// services.AddSingleton<IMessageWriter, MessageWriter>();
// 在註冊服務時更改 IMessageWriter 的實現,無需修改每一處業務代碼
services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

Worker類只依賴於IMessageWriter介面,不去關注如何實現,如何生成,這些事情由框架去做。我們要做的只是重新寫一個實現類,然後修改註冊服務的代碼即可。

參考鏈接#

.NET 中的依賴關係注入

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。