zoukankan      html  css  js  c++  java
  • Ninject之旅之七:Ninject依赖注入

    摘要

    可以使用不同的模式向消费者类注入依赖项,向构造器里注入依赖项是其中一种。有一些遵循的模式用来注册依赖项,同时有一些需要避免的模式,因为他们经常导致不合乎需要的结果。这篇文章讲述那些跟Ninject功能相关的模式和反模式。然而,全面的介绍可以在Mark Seemann的书《Dependency Injection in .NET》中找到。

    1、构造函数注入

    构造函数时推荐的最常用的向一个类注册依赖项的模式。一般来说,这种模式应该经常被用作主要的注册模式,除非我们不得不使用其他的模式。在这个模式中,需要在构造函数中引入所有类依赖项列表。

    问题是如果一个类有多于一个的构造函数会怎么样。尽管Ninject选择构造函数的策略是可以订制的,他默认的行为是选择那个有更多可以被Ninject解析的参数的构造函数。

    因此在下面的例子中,尽管第二个构造函数有更多的参数,如果Ninject不能解析IService2,他将调用第一个构造函数。如果IService1也不能被解析,他将调用默认构造函数。如果两个依赖项都被注册了可以被解析,Ninject将调用第二个构造函数,因为他有更多的参数。

     1 public class Consumer
     2 {
     3   private readonly IService1 dependency1;
     4   private readonly IService2 dependency2;
     5   public Consumer(IService1 dependency1)
     6   {
     7     this.dependency1 = dependency1;
     8   }
     9   public Consumer(IService1 dependency1, IService2 dependency2)
    10   {
    11     this.dependency1 = dependency1;
    12     this.dependency2 = dependency2;
    13   }
    14 }

    如果上一个类中有另一个构造函数也有两个可以被解析的参数,Ninject将抛出一个ActivationException异常,通知多个构造函数有相同的优先级。

    有两种方式可以重载默认的行为,显示地选择调用哪一个构造函数。第一种方式是在绑定中指出需要的构造函数:

    Bind<Consumer>().ToConstructor(arg => new Consumer(arg.Inject<IService1>()));

    另一种方式是在需要的构造函数中使用[Inject]特性:

    1 [Inject]
    2 public Consumer(IService1 dependency1)
    3 {
    4   this.dependency1 = dependency1;
    5 }

    在上面的例子中,我们再第一个构造函数中使用了[Inject]特性,显式地指定在初始化类中注入依赖项时调用的构造函数。尽管第二个构造函数有更多的参数,按照Ninject默认的策略会选择第二个构造函数。

    注意在多个构造函数中同时使用这个特性时会产生ActivationException异常。

    2、初始化方法和属性注入
    除了构造函数注入之外,Ninject支持通过初始化方法和属性setter依赖注入。我们可以通过[Inject]特性指定任意多的需要的方法和属性来注入依赖项。

    尽管依赖项在类一初始化的时候就被注入,但是不能预计依赖项注入的顺序。下面的例子演示如何指定一个属性的注入:

    1 [Inject]
    2 public IService Service
    3 {
    4   get { return dependency; }
    5   set { dependency = value; }
    6 }

    下面的例子使用注入方法注册依赖项:

    1 [Inject]
    2 public void Setup(IService dependency)
    3 {
    4     this.dependency = dependency;
    5 }

    注意只有公有成员和公有构造函数才可以被注入,甚至internal的成员都被忽视除非Ninject配置成可以注册非公有成员。

    在构造函数注入中,构造函数是单一的点,在这个点上,类被初始化后就可以使用它的所有的依赖项。但是,如果我们使用初始化方法,依赖项通过多个点以无法预期的顺序被注入。因此,不能知道在哪个方法中,所有的依赖项都已经被注入可以使用了。为了解决这个问题,Ninject提供了IInitializable接口。这个接口有一个IInitialize方法,一旦所有的依赖项都被注入,将调用这个方法:

     1 public class Consumer : IInitializable
     2 {
     3     private IService1 dependency1;
     4     private IService2 dependency2;
     5     [Inject]
     6     public IService Service1
     7     {
     8         get { return dependency1; }
     9         set { dependency1 = value; }
    10     }
    11     [Inject]
    12     public IService Service2
    13     {
    14       get { return dependency2; }
    15       set { dependency2 = value; }
    16     }
    17     public void Initialize()
    18     {
    19       // Consume all dependencies here
    20     }
    21 }

    尽管Ninject支持使用属性和方法注入,构造函数注入应该是优先的方式。首先,构造函数注入使类更好的重用性,因为所有的类依赖项的列表是可见的。在初始化属性或方法里,类的使用者需要研究类的所有的成员或者浏览了类说明文档后(如果有的话),才能发现他的依赖项。
    当使用构造函数注入的时候,类的初始化更容易。因为所有的依赖项在同一时刻被注入,我们可以很容易地在相同的地方使用这些依赖项。正如我们在前面的例子中看到的那样,在构造函数注入的场景中,注入的字段可能是只读的。因为只读字段只能在构造函数中被初始化,我们需要将他改成可写的,才可以使用初始化方法和属性对他进行注入。这将导致潜在的修改字段可读写属性的问题。
    3、服务定位器

    服务定位器是Martin Fowler介绍的一种很有争议的设计模式。尽管在一些特定的场景中可能有用,他一般被认为是一种反模式,需要尽可能避免。如果我们不属性这个模式,Ninject很容易地被误用成服务定位器。下面的例子示范误用Ninject kernal成一个服务定位器而不是一个DI容器:

     1 public class Consumer
     2 {
     3   public void Consume()
     4   {
     5     var kernel = new StandardKernel();
     6     var depenency1 = kernel.Get<IService1>();
     7     var depenency2 = kernel.Get<IService2>();
     8     ...
     9   }
    10 }

    前面的代码有两个重大的缺点。第一个是尽管我们使用一个DI容器,但是我们不可能一直使用DI。这个类跟Ninject kernal绑在一起,然而Ninject kernal并不真的是这个类的依赖项。这个类以及他所有预期的调用类将总是不必要的依赖于kernal对象和Ninject类库。在另一方面,真正的类依赖项(IService1和IService2)对于这个类却不可见,降低了可重用性。即使我们按照下面的方式修改这个类的设计,这个问题仍然存在:

     1 public class Consumer
     2 {
     3   private readonly IKernel kernel;
     4   public Consumer(IKernel kernel)
     5   {
     6     this.kernel = kernel;
     7   }
     8   public void Consume()
     9   {
    10     var depenency1 = kernel.Get<IService1>();
    11     var depenency2 = kernel.Get<IService2>();
    12     ...
    13   }
    14 }

    前面的类仍旧依赖于Ninject类库,然后他不必要这样,他真正的依赖项仍然对调用者不可见。可以很容易地使用构造函数注入模式重构:

    1 public Consumer(IService1 dependency1, IService2 dependency2)
    2 {
    3   this.dependency1 = dependency1;
    4   this.dependency2 = dependency2;
    5 }

     

  • 相关阅读:
    动态字节码技术Javassist
    自己实现简单版的注解Mybatis
    AOP实现事务和记录日志
    自己实现简单版SpringMVC
    静态变量
    docker安装nginx , 安装mysql5.6,安装redis3.2
    Worker Thread模式
    linux 安装jdk
    dockfile构建自己的tomcat
    docker使用2
  • 原文地址:https://www.cnblogs.com/uncle_danny/p/6044697.html
Copyright © 2011-2022 走看看