Ninject之旅之七:Ninject依赖注入

Posted 丹尼大叔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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]特性指定任意多的需要的方法和属性来注入依赖项。

Although the dependencies will be injected to them as soon as the class is
constructed, it is not possible to predict in which order they will receive their
dependencies. The following code shows how to specify a property for injection:

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

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 }

 

以上是关于Ninject之旅之七:Ninject依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

Ninject之旅之八:Ninject插件模型

使用Ninject进行DI(依赖注入)

依赖注入(DI)和Ninject,Ninject

中等信任中 MVC 控制器 + 依赖注入 (Ninject) 的问题

IOC框架之Ninject 简介

IOC框架之Ninject 简介