之前记录的依赖注入太复杂,今天认真看了《C#高级编程》里面对依赖注入的解释,明显就简单很多。
1、什么是依赖注入?为什么需要它? 更快的开发周期需要单元测试和更好的可更新性。更改一些代码,不应该导致意外位罝出现错误。创建更模块化的、减少依赖项的应用程序,有助于防止这种错误。
依赖注入(Dependency Injection,DI)允许从类的外部注入依赖项,因此注入依赖项的类只需要知道一个协定(通常是C#接口)。这个类可以独立于其对象的创建。
依赖注入更便于进行单元测试。在单元测试中,只需要测试特定的类,需要的依赖项可以替换为包含测试数据的特殊模拟类。
还可以使用不同的实现区分生产模式和开发模式。例如,在生产过程中,可能需要访问SAP服务器,或者可能需要对所有开发人员都无法访问的特定活动目录进行身份验证。在开发的每个调试会话期间,都不希望等待成功的身份验证,也不需要SAP服务器开发用户界面。在这里,可以给相同的协定使用不同的实现来模拟身份验证,可以使用测试数据而不是访问SAP服务器。
也可以在不同的平台上使用不同的实现。例如,可以创建一个.NET标准库,在其中为UWP、WPF和Xamarin应用程序实现所有公共功能,并可以根据需要重定向到特定于平台的代码。
依赖注入还允许用自定义特性替换标准功能。ASP.NETCore和EntityFrameworkCore主要基于依赖注入。这些技术使用数百个协定一例如,来找到控制器,将HTTP请求映射到控制器,将接收到的数据转换为参数,将数据库表映射到实体类型等。使用不同的实现,可以轻松地替换自定义功能。
DI是敏捷软件开发和持续软件交付实践的核心模式。
依赖注入不需要依赖注入容器,但该容器有助于管理依赖项。 依赖注入容器管理的服务列表越来越长,就可以看到它的优点。ASP.NETCore和EntityFrameworkCore使用Microsoft.Exteosions.DependencyInjection作为容器来管理所有依赖项,以此管理数百个服务。
尽管依赖注入和依赖注入容器在非常小的应用程序中会增加复杂性,但是一旦应用程序变得更大,需要多个服务,依赖注入就会降低复杂性,并促进非紧密绑定的实现。
2、没有依赖注入 下面的示例没有使用依赖注入;稍后将更改它,以使用依赖注入。所用的服务实现在类GreetingService中定义。这个类定义了返回字符串的Greet方法:
public class GreetingService { public string Greet(stringname)=>$"Hello,{name}"; } 类HomeController使用这个服务。在Hello方法中,实例化了GreetingService,并且调用Greet方法:
public class HomeController { public string Hello(string name) { var service = new GreetingService(); return service.Greet(name); } } 下面看看Program类的Main()方法。其中实例化了HomeController,调用Hello方法,将结果写入控制台:
static void Main() { var controller=new HomeController(); string result=controller.Hello("Stephanie"); Console.WriteLine(result); } 程序运行时,把Hello,Stephanie写入控制台。这有什么问题吗?
HomeController和GreetingService是紧密稱合的。要用不同的实现取代HomeController中的GreetingService并不容易。 这个GreetingService是一个返回字符串的简单服务。在正常的应用程序中,场景通常更复杂。例如,GreetingService可能使用HTTP请求访问API服务,或者使用EntityFramewoik访问数据库。可能要更改在一个地方使用的服务,而不是査找使用服务的所有位置。
另外,为HomeController创建单元测试时,也会测试GreetingService。在单元测试中,希望仅测试单个类的方法的功能,而不需要使用其他依赖项。在HomeController中,不能很容易地为单元测试替换GreetingService。从技术上讲,为单元测试替换GreetingService方法的内部实现是可能的。使用Microsoft Fakes框架,可以通过替换GreetingSeivice类的特定方法和属性,来更改方法的实现。这个变更是在单元测试中定义的,并且只有在单元测试运行时才会发生:通过另一个方法来“伪造”原来的方法。其实这有更好的方法:使用依赖注入。
下一节将介绍如何更改此实现,以使用依赖注入。
3、使用依赖注入实现 下面使HomeController独立于GreetingService的实现。为此,可以创建接口IGreetingService,它定义了HomeController所需的功能:
public interface IGreetingService { string Greet(stringname); } GreetingService现在实现了接口IGreetingService:
public class GreetingService:IGreetingService { public string Greet(stringname)=>$"Hello,{name}"; } HomeController现在只需要对一个对象的引用,该对象实现了接口IGreetingService。它用HomeController的构造函数注入,分配给私有字段,通过方法Hello来使用:
public class HomeController { private readonly IGreetingService _greetingService; public HomeController(IGreetingService greetingService) { _greetingService=greetingService?? throw new ArgumentMullException(nameof(greetingService)); } public string Hello(stringname) => _greetingService.Greet(name); } 在这个实现中,HomeController利用了控制反转的设计原理。HomeController没有像以前那样实例化GreetingService。相反,定义由HomeController使用的具体类的控件在外部给出;换句话说,控制是反转的。
...