本文示例代碼,均採用 .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
類,想換一種實現,那麼也要修改代碼,兩個類之間的耦合非常緊密。
為了解開二者之間的耦合,我們根據下面的幾個步驟,使用依賴注入,做到控制反轉:
- 將
MessageWriter
類的行為抽象出一個介面IMessageWriter
; - 在容器中註冊服務,將
IMessageWriter
的實現註冊為MessageWriter
; - 將
_messageWriter
的類型修改為介面IMessageWriter
,Worker
類只依賴介面;
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
介面,不去關注如何實現,如何生成,這些事情由框架去做。我們要做的只是重新寫一個實現類,然後修改註冊服務的代碼即可。