本文示例代码,均采用 .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
类只依赖接口;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 中的依赖关系注入