第三章 Dependency injection

实例化一个类的时候,它的构造函数会被调用,构造的时候我们有可能会传入参数。这就是一个简单的Dependency injection(DI)的例子,这种被称为constructor injection,即构造时注入。除了这种构造方式之外,还有property injectionmethod call injection,不过这两种不太常见。

如果我们指定传入的参数是一个接口而不是具体的某个类,那么从某种意义上来说你也就降低了代码之间的耦合度。通常来说我们使用要给容器(container)来维护接口、抽象类与他们对应实现类或子类的映射关系。

Introduction to dependency injection:DI简介

DI是IoC模式的一个特殊版,其中被倒置的是获取所需依赖关系的过程。在运行时,一个类负责将所需的依赖注入到对象中。看一下DI的简单例子:

public class ProfileViewModel : ViewModelBase 
{ 
    private IOrderService _orderService; 
    public ProfileViewModel(IOrderService orderService) 
    { 
        _orderService = orderService; 
    } 
    ...  
}

ProfileViewModel类的唯一依赖就是这个IOrderService接口,而且构造时接受一个IOrderService类型的实例,这个实例会被另一个类进行注入。这个类称为dependency injection container(DI容器),她负责创建实现IOrderService接口的对象,并将这个对象注入到ProfileViewModel的构造函数中。DI容器经过简单的配置之后,就可以自动创建对应的实例并管理这些实例的生命周期,进而降低对象之间的耦合度。当一个对象要被创建时(在这里是创建ProfileViewModel对象时),DI容器会注入它所需要的依赖。如果这些依赖里有些还没被DI容器创建,则首先创建并解析它们的依赖项(即也会创建依赖的依赖)。

注意
虽然依赖注入也可以通过工厂来实现,但是ID容器提供了其它额外的功能,如:生命周期管理,通过程序集扫描注册。

现在又很多的DI容器可供我们使用,如Autofac、Ninject等。这里以Autofac为例。在运行时,DI容器需要知道具体实例化哪个类,因为实现IOrderService的类可能有好多个。这个过程就包括了两方面:

  • Registration:DI容器决定如何实例化接口的实现类。
  • Resolutiion:DI容器去实例化这个类。

最后,你就可以正常使用ProfileViewModel对象了,在这个对象没有其它引用的时候垃圾收集器也可以正常回收它。

Registration:注册

有两种方式可以实现类型注册:

  • 注册一种类型以及它的映射。当被请求时,DI容器会实例化这个类型。
  • 注册一个存在的对象作为单例模式。当被请求时,DI容器会直接返回这个已存在对象的引用。

提醒:依赖注入并不是在任何场景下都适用
依赖注入增加了额外的复杂性和要求,小的app不一定适用。如果一个类不需要任何依赖,或者也不被任何类型依赖,那么就不需要DI。除此之外,如果一个类的依赖是固定的,不会经常改变,那么也不需要DI。

在app生命周期一开始的时候就要进行类型注册。在eShopOnContainers这个例子中,类型注册是在ViewModelLocator里,创建了一个IContainer的对象,整个app中只有这一个引用。

private static IContainer _container;
var builder = new ContainerBuilder();

builder可以注册类型和实例,通常的注册形式如下:

builder.RegisterType<RequestProvider>().As<IRequestProvider>();

RegisterType方法反应了接口类型和具体类型的映射关系。它告诉DI容器当需要注入IRequestProvider时,就创建一个RequestProvider的实例,并注入。
具体的类型也可以不通过指定映射关系而直接注册,如:

builder.RegisterType<ProfileViewModel>();

当ProfileViewModel被解析后,DI容器会注入它所需要的依赖。

Autofac还支持单例注册,DI容器负责维护一个唯一的引用。如:

builder.RegisterType<OrderService>().As<IOrderService>().SingleInstance();

这样所有依赖的对象都会使用这一个实例,这个实例被app中所有的对象所共享。

除了通过RegisterType注册外还可以通过RegisterInstance方法注册,如:

builder.RegisterInstance(new OrderMockService()).As<IOrderService>();

这里new了一个OrderMockService然后注册到了DI容器里,整个DI容器内只有这一个实例,它也是单例的。

接下来就是build的容器了:

_container = builder.Build();

这样就创建了一个新的DI容器,这个容器包括了我们刚才创建的所有registration。

提醒:容器创建好之后就不要再修改或更新了,否则会引起问题。

Resolution:解析

类型一旦注册之后,它就可以作为依赖被解析或注入。当一个类型被解析时,一般会有下面三个步骤:

  1. 如果这个类型没有注册过,则抛出异常。
  2. 如果这个类型以单例模式注册的,则DI容器会返回这个单例。如果这个单例没有被创建过,则先创建这个单例,并维护它的一个引用。
  3. 如果类型不是单例模式注册的,DI容器就实例化这个类型,且不维护它的引用。

解析的代码如下:

var requestProvider = _container.Resolve<IRequestProvider>();

通常,当请求一个具体类型的实例时,Resolve方法会被调用。以下代码是eShopOnContainers app实例化viewModel和依赖:

var viewModel = _container.Resolve(viewModelType);

注意:在容器里注册和解析类型会有性能消耗,因为DI容器是通过反射来创建实例的。如果依赖的层级很深的话,那么创建实例的性能消耗会有明显增加。

Managing the lifetime of resloved objects:管理解析对象的生命周期

在类型注册之后,当需要解析时或者当依赖机制需要将实例注入到其他类时,Autofac的默认行为是创建一个这个类型的新的实例。在这种情况下,DI容器不会维持这个对象的引用,但是使用RegisterInstance方法注册时,因为注册的是单例,所以DI容器会一直持有这个对象的引用,当DI容器离开作用域或者被显示释放时,这个对象才会被释放。