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 中的依赖关系注入

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。