本文示例代码,均采用 .NET 6,具体的代码可以在这个仓库 Articles.DI 中获取。
在 .NET 中的依赖关系注入是一等公民,官方框架了提供配置、日志记录和选项等模式。
依赖注入(DI)通过下面的方式,解决了前面的这些问题:
- 使用接口或基类,将依赖关系实现抽象化;
- 使用统一容器注册服务;
- 将服务注入到使用它的类中,由框架负责服务的实例化,且由框架管理它的声明周期;
我们用一个示例,来展示如何在 .NET 中,使用依赖注入。下面这个示例是一个后台服务Worker,其依赖MessageWriter,每隔 1s,调用 MessageWriter.Write 输出一行简单的日志。
使用依赖注入#
前面的代码中,Worker类显式依赖MessageWriter类,且在自己的代码块中,自己实例化了对应实例。
随着项目的复杂度增加,Worker类包含多个不同变量,那么都要Worker类自行实例化,而且若是这些变量又依赖其他服务,那么光是初始化的代码就会非常的繁杂。并且后续不使用MessageWriter类,想换一种实现,那么也要修改代码,两个类之间的耦合非常紧密。
为了解开二者之间的耦合,我们根据下面的几个步骤,使用依赖注入,做到控制反转:
- 将
MessageWriter类的行为抽象出一个接口IMessageWriter; - 在容器中注册服务,将
IMessageWriter的实现注册为MessageWriter; - 将
_messageWriter的类型修改为接口IMessageWriter,Worker类只依赖接口;
4.Worker类在构造函数中,声明需要的类型变量,由容器进行注入;
上述代码中,Worker类和MessageWriter类之间并没有直接联系,前者拥有一个IMessageWriter类型的成员,容器服务会通过构造函数注入一个实例进来,Worker类可以直接使用,而无需知道如何构造一个该类型的实例。后者MessageWriter是一个实现IMessageWriter接口的类,它只需要根据接口要求,实现对应方法,不用去管其他人员会如何使用它,而在容器注册服务时,将MessageWriter注册为IMessageWriter的实现,那么在有服务依赖于IMessageWriter时,容器会自动生成一个MessageWriter实例注入进去。
依赖注入将两个类解耦,后续如果想要更换IMessageWriter的具体实现,可以直接添加一个新的实现类,然后修改注册即可,可以参考下面的代码:
Worker类只依赖于IMessageWriter接口,不去关注如何实现,如何生成,这些事情由框架去做。我们要做的只是重新写一个实现类,然后修改注册服务的代码即可。