本文示例代码,均采用 .NET 6,具体的代码可以在这个仓库 Articles.DI 中获取。
让我们看这么一段简单的代码:
// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService1 public class MessageWriter { public void Write(string message) => Console.WriteLine(message); } public class Worker : BackgroundService { private readonly MessageWriter _msgWriter = new MessageWriter(); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _msgWriter.Write($"Worker running at: {DateTime.Now}"); await Task.Delay(1000, stoppingToken); } } }
在上面的代码中,Worker
类包含有一个 MessageWriter
类的实例成员,这个类创建并直接依赖于 MessageWriter
类。这样的代码有一些问题:
_msgWriter
变量要替换成别种类型的实现,需要修改 Worker
类;MessageWriter
类具有其他依赖项,则必须在 Worker
类中进行配置;Worker
类进行单元测试。如果有一个统一的管理者,负责 MessageWriter
类型的实例化,然后将其传递给有需求的类,就能做到这两个类之间的依赖解除,减少了硬编码。而这,就是依赖注入。
依赖注入(英文 Dependency Injection,简称 DI),是一种在类及其依赖项之间实现控制反转(英文 Inversion of Control,简称 IoC)的技术。
而控制反转,是面向对象编程中的一种设计原则,可以用来减少程序代码间的耦合程度。除了依赖注入外,还有一种实现控制反转的方法,叫做“依赖查找”。
大多数程序都由多个类相互引用,彼此依赖合作以实现业务逻辑,这使得每个对象都需要获取其他服务的引用。如果这个获取过程需要靠对象自己实现,那么这将导致类之间高度耦合,难以维护和调试。
传统应用中,如果 A 服务要获取 B 服务,则需要在自己的代码块中实例化。而如果使用依赖注入,A 服务只需声明所需的服务,由外部容器来负责生成实例,A 服务只需接收即可。整个过程从主动变成了被动,反转了对象的获取过程,即实现了控制反转。
下面两张图展示了两种依赖项关系图:
直接依赖项关系:类 A 调用类 B 的方法,类 B 调用类 C 的方法,则在编译时,类 A 依赖与类 B,类 B 依赖与类 C。
应用依赖反转后,类 A 引用的是接口 IB 的方法,不再直接依赖于类 B。在编译时,则是类 A 和类 B 引用了接口 IB,和前面的直接依赖相比,反转了依赖项。而类 B 和类 C 之间也是相似的。
依赖注入 - 维基百科
控制反转 - 维基百科
依赖关系反转 - 现代 ASP.NET Web 应用程序电子书
.NET 中的依赖关系注入
Inversion of Control Containers and the Dependency Injection pattern
依赖注入和控制反转的理解
控制反转(IoC)与依赖注入(DI)