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

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。