[ASP.NET Core 3框架揭秘] 依赖注入:依赖注入模式
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制权从应用转移到框架之中以实现对流程的复用,并按照“好莱坞法则”实现应用程序的代码与框架之间的交互。我们可以采用若干设计模式以不同的方式实现IoC,比如我们在前面介绍的模板方法、工厂方法和抽象工厂,接下来我们介绍一种更有价值的IoC模式:依赖注入(DI:Dependency Injection)。
一、由容器提供对象
和前面介绍的工厂方法和抽象工厂模式一样,依赖注入是一种“对象提供型”的设计模式,在这里我们将提供的对象统称为“服务”、“服务对象”或者“服务实例”。在一个采用依赖注入的应用中,我们定义某个类型的时候,只需要直接将它依赖的服务采用相应的方式注入进来就可以了。
在应用启动的时候,我们会对所需的服务进行全局注册。一般来说,服务大都是针对实现的接口或者继承的抽象类进行注册的,服务注册信息的帮助我们在后续消费过程中提供对应的服务实例。按照“好莱坞法则”,应用只需要定义并注册好所需的服务,服务实例的提供则完全交给框架来完成,框架则会利用一个独立的“容器(Container)”来提供所需的每一个服务实例。
我们将这个被框架用来提供服务的容器称为“依赖注入容器”,也有很多人将其称为“IoC容器”,根据前面针对IoC的介绍,我不认为后者是一个合理的称谓。依赖注入容器之所以能够按照我们希望的方式来提供所需的服务是因为该容器是根据服务注册信息来创建的,服务注册了包含提供所需服务实例的所有信息。
举个简单的例子,我们创建一个名为Cat的依赖注入容器类型,那么我们可以调用如下这个扩展方法GetService<T>从某个Cat对象中获取指定类型的服务对象。我之所以将其命名为Cat,源于我们大家都非常熟悉的一个卡通形象“机器猫(哆啦A梦)”。机器猫的那个四次元口袋就是一个理想的依赖注入容器,大熊只需要告诉哆啦A梦相应的需求,它就能从这个口袋中得到相应的法宝。依赖注入容器亦是如此,服务消费者只需要告诉容器所需服务的类型(一般是一个服务接口或者抽象服务类),就能得到与之匹配的服务实例。
public static class CatExtensions { public static T GetService<T>(this Cat cat); }
对于我们演示的MVC框架来说,我们在前面分别采用不同的设计模式对框架的核心类型MvcEngine进行了“改造”,现在我们采用依赖注入的方式,并利用上述的这个Cat容器按照如下的方式对其进行重新实现,我们会发现MvcEngine变得异常简洁而清晰。
public class MvcEngine { public Cat Cat { get; } public MvcEngine(Cat cat) => Cat = cat; public async Task StartAsync(Uri address) { var listener = Cat.GetService<IWebListener>(); var activator = Cat.GetService<IControllerActivator>(); var executor = Cat.GetService<IControllerExecutor>(); var renderer = Cat.GetService<IViewRenderer>(); await listener.ListenAsync(address); while (true) { var httpContext = await listener.ReceiveAsync(); var controller = await activator.CreateControllerAsync(httpContext); try { var view = await executor.ExecuteAsync(controller, httpContext); await renderer.RenderAsync(view, httpContext); } finally { await activator.ReleaseAsync(controller); } } } }
依赖注入体现了一种最为直接的服务消费方式,消费者只需要告诉提供者(依赖注入容器)所需服务的类型,后者就能根据预先注册的规则提供一个匹配的服务实例。由于服务注册最终决定了依赖注入容器根据指定的服务类型会提供一个怎样的服务实例,所以我们可以通过修改服务注册的方式来实现对框架的定制。如果应用程序需要采用前面定义的SingletonControllerActivator以单例的模式来激活目标Controller,那么它可以在启动MvcEngine之前按照如下的形式将SingletonControllerActivator注册到依赖注入容器上就可以了。
public class App { static void Main(string