摘要

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

1、构造函数注入

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

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

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

 public class Consumer
{
  private readonly IService1 dependency1;
  private readonly IService2 dependency2;
  public Consumer(IService1 dependency1)
  {
    this.dependency1 = dependency1;
  }
9   public Consumer(IService1 dependency1, IService2 dependency2)
  {
    this.dependency1 = dependency1;
    this.dependency2 = dependency2;
  }
}

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

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

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

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

 [Inject]
public Consumer(IService1 dependency1)
{
  this.dependency1 = dependency1;
}

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

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

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

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

 [Inject]
public IService Service
{
  get { return dependency; }
  set { dependency = value; }
}

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

 [Inject]
public void Setup(IService dependency)
{
this.dependency = dependency;
}

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

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

 public class Consumer : IInitializable
{
private IService1 dependency1;
private IService2 dependency2;
[Inject]
public IService Service1
{
get { return dependency1; }
set { dependency1 = value; }
}
[Inject]
public IService Service2
{
  get { return dependency2; }
  set { dependency2 = value; }
}
public void Initialize()
{
  // Consume all dependencies here
}
}

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

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

 public class Consumer
{
  public void Consume()
  {
    var kernel = new StandardKernel();
    var depenency1 = kernel.Get<IService1>();
    var depenency2 = kernel.Get<IService2>();
    ...
  }
}

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

 public class Consumer
{
  private readonly IKernel kernel;
  public Consumer(IKernel kernel)
  {
    this.kernel = kernel;
  }
  public void Consume()
  {
    var depenency1 = kernel.Get<IService1>();
    var depenency2 = kernel.Get<IService2>();
    ...
  }
}

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

 public Consumer(IService1 dependency1, IService2 dependency2)
{
  this.dependency1 = dependency1;
  this.dependency2 = dependency2;
}

Ninject之旅之七:Ninject依赖注入的更多相关文章

  1. (依赖注入框架:Ninject ) 一 手写依赖注入

    什么是依赖注入? 这里有一个场景:战士拿着刀去战斗: 刀: class Sword { public void Hit(string target) { Console.WriteLine($&quo ...

  2. Ninject之旅目录

    第一章:理解依赖注入 Ninject之旅之一:理解DI 第二章:开始使用Ninject Ninject之旅之二:开始使用Ninject(附程序下载) Ninject之旅之三:Ninject对象生命周期 ...

  3. 6.在MVC中使用泛型仓储模式和依赖注入实现增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  4. 在MVC中使用泛型仓储模式和依赖注入实现增删查改

    标签: 原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository ...

  5. [ASP.NET MVC 小牛之路]04 - 依赖注入(DI)和Ninject

    本人博客已转移至:http://www.exblr.com/liam  为什么需要依赖注入 在[ASP.NET MVC 小牛之路]系列的理解MVC模式文章中,我们提到MVC的一个重要特征是关注点分离( ...

  6. 依赖注入(DI)和Ninject,Ninject

    我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用.这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,I ...

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

    Ninject是一个快如闪电.超轻量级的基于.Net平台的依赖注入框架.它能够帮助你把应用程序分离成一个个松耦合.高内聚的模块,然后用一种灵活的方式组装起来.通过使用Ninject配套你的软件架构,那 ...

  8. Ninject依赖注入——构造函数的注入

    1.Ninject简介 Ninject是基于.Net平台的依赖注入框架,它能够将应用程序分离成一个个高内聚.低耦合(loosely-coupled, highly-cohesive)的模块,然后以一种 ...

  9. <Pro .NET MVC4> 三大工具之依赖注入神器——Ninject

    这篇内容是对<Pro .NET MVC4>一书中关于Ninject介绍的总结. Ninject是.NET MVC的一款开源的依赖注入工具. 使用场景:当MVC项目中使用了依赖注入技术来给程 ...

随机推荐

  1. 【转载】.NET模拟POST登录并保持登录状态

    好了,还是由于工作需要 要登录一个网站并且模拟点击下载某些东西 原理就是先对一个地址(地址是用户名和密码输入框所在的form的action对应的页面)进行POST提交用户名和密码(不考虑验证码,当然验 ...

  2. LINUX中如何查看某个进程打开的网络链接有多少

    使用lsof命令,比如查看sshd这个程序的网络连接使用命令 lsof -i | grep ^sshd

  3. linux find命令

    Linux中find常见用法示例 ·find   path   -option   [   -print ]   [ -exec   -ok   command ]   {} \; find命令的参数 ...

  4. 将.war文件解压到指定目录

    jar命令无法将.jar解压到指定目录,因为-C参数只在创建或更新包的时候可用 要将.jar文件解压到指定目录可以用unzip命令 unzip命令在windows下自带就有,不用另外下载安装 下面是将 ...

  5. MVC视图请求流程视图

    /*         *视图请求流程         *当接受到home/index请求时         *先去找viewstart.cshtml视图,再去加载index.cshtml视图      ...

  6. 用word制作电子书最简捷模式 支持epub和mobi目录

    因为制作一本OCR的电子书,转到word编辑排版后,用calibre转成mobi发现没有目录,在网上查了资料研究了一下,终于解决了目录问题,根本不用将word文档转换为什么htm或txt,尤其是转换t ...

  7. Weblogic是瓦特?和JVM是瓦特关系?

    所谓固定内存60M是瓦特? 以下内容是个瓦特? “总内存大小=堆内存+非堆内存1200m:为堆内存大小,如果不指定后者参数则有最大数限制,网上很多文章认为这就是JVM内存,-Xmx为设置最大堆内存60 ...

  8. VR定制 AR定制 就找北京动软VR开发团队(VR案例 AR案例)

    我们长期承接丰交互软件.游戏项目外包: VR/AR内容应用定制.VR.AR游戏项目外包(有主流测试硬件设备) VR全景应用.视频外包 请提供贵公司的信息,我们将提供高大上的VR案例欢迎联系我们给您提供 ...

  9. LeetCode "Largest Divisible Subset" !

    Very nice DP problem. The key fact of a mutual-divisible subset: if a new number n, is divisible wit ...

  10. 解决在VS2015下用C++开发的DLL在WIN7上无法加载运行

    首先用Dependency Walker检查该DLL依赖的库,如下图所示: 依赖的动态库除了KERNEL32.DLL.USER32.DLL外,还包括了MSVCP120D.DLL以及MSVCR120D. ...