如何在企业项目中使用依赖注入

时间:2012-06-12 21:34:36

标签: c# architecture dependency-injection inversion-of-control

想象一下,你有一个应用程序,有数百个类实现了几十个“高级”接口(意味着组件级别)。是否有推荐的依赖注射方式(如团结)。是否应该有一个可以用于自举的“通用容器”,可以作为Singleton访问?是否应该传递一个容器,所有实例都可以在RegisterInstance中传递?应该在启动时的某个地方通过RegisterType完成所有事情吗?如何在需要时使容器可以访问。构造函数注入似乎是错误的,作为标准方式的争论,您必须将接口从组件级别传递到在启动时使用它的非常低的位置,或者引用最终在“知道您居住的地方”反模式。 而且:拥有一个“可用”容器可能会让开发人员想到在客户端环境中解析服务器组件。怎么避免呢?

欢迎任何讨论!


编辑以澄清

我想出了一个有点真实世界的例子,可以更好地了解我所看到的问题。

让我们想象一下应用程序是一个高保真系统。 系统有CD播放器(集成在CD机架中)和 一个USB端口(集成在USB机架中),用于播放音乐。

现在,cd播放器和usb端口应能播放mp3音乐。

我周围有一个mp3解码器,可以注射。

现在我开始使用高保真系统。还没有插入CD 没有usb棒插入。我现在不需要mp3解码器。

但是使用构造函数注入,我必须已经注入 依赖于cd机架和USB机架。

如果我从不插入mp3 CD或mp3 usb棒怎么办?

我应该在cd机架中持有一个引用,所以当一个mp3 cd 插入,我手头有一个装饰? (对我来说似乎不对)

cd机架的子系统需要解码器 仅在插入mp3时才会启动。现在我没有容器了 在CD机架中,这里的构造函数注入怎么样?

4 个答案:

答案 0 :(得分:6)

首先,Dependency Injection是一种不需要容器的设计模式。 DI模式表明:

  

依赖注入是一种软件设计模式,允许在运行时选择组件而不是编译时间

以Guice(java依赖注入框架)为例,在Java中Guice是一个DI框架,但它本身不是容器。

<。> .Net中的大多数DI工具实际上都是容器,因此您需要填充容器以便能够注入依赖项

我不喜欢每次在容器中注册每个组件的想法,我只是讨厌这个。有几种工具可以帮助您根据约定自动注册组件,我不使用Unity,但我可以举例说明NinjectAutoFac

我实际上是根据惯例使用几乎任何DI工具编写small utility自动注册组件,它仍处于开发阶段

关于您的问题:

  

是否应该有一个可以用于自举的“通用容器”,可以作为Singleton访问?

答案是肯定的,(有一个工具来抽象使用的DI工具,它被称为ServiceLocator)这就是DI工具的工作方式,应用程序可以使用静态容器,但不建议这样做在域对象中使用它来创建实例,这被认为是anti-pattern

BTW我发现这个工具在运行时注册组件非常有用:

http://bootstrapper.codeplex.com/

  

是否应该传递一个容器,所有实例都可以在RegisterInstance中传递?

没有。这将违反law of Demeter。如果决定使用容器,最好在应用程序启动时注册组件

  

如何在需要时使容器可以访问

使用Common Service Locator你可以在应用程序的任何地方使用它,但就像我说的那样,不建议在域对象中使用它来创建所需的实例,而是在构造函数中注入对象。对象并让DI工具自动注入正确的实例。

现在基于此:

  

构造函数注入似乎是错误的,作为标准方式的争论,你必须将接口从组件级别传递到在启动时使用它的最低位置,或者引用最终在“知道你在哪里活“反模式”

让我觉得你没有为你的应用程序编写严重的单元测试,这很糟糕。所以我的建议是,在选择您要使用的DI工具之前,或者在考虑所有答案之前,请考虑以下问题,请参考以下链接,重点关注一件事:编写干净的可测试代码,这是迄今为止您可以自己回答自己问题的最佳来源

清洁代码谈判:

文章

以下链接强烈推荐

http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/

http://www.loosecouplings.com/2011/01/how-to-write-testable-code-overview.html

答案 1 :(得分:2)

首先,有组合根模式,您可以尽快设置依赖项,桌面中的Main(),web中的global.asax / app_start。然后,依赖构造函数注入,因为这会使您的依赖关系变得清晰。

但是,你仍然需要一些实际上RESOLVE依赖的东西。我至少知道两种方法。

第一个是使用服务定位器(这几乎等于使容器成为单例)。这被认为是很多人的反模式,但它只是很好用。无论什么时候需要服务,都要问你的容器:

 ... business code...
 var service = ServiceLocator.Current.GetInstance<IMyService>();
 service.Foo();  

Service Locator只使用您在Composition Root中设置的容器。

另一种方法是依靠可用作单例的对象工厂,并将容器注入其中:

 var service = IMyServiceFactory.Instance.CreateService();

技巧是工厂的实现在内部使用容器来解析服务。但是,这种方法具有额外的工厂层,使您的业务代码独立无意识的IoC!您可以自由地将工厂重新设计为不在内部使用 IoC,并且仍然维护每一行业务代码。

换句话说,它只是隐藏工厂层后面的容器/定位器的技巧。

虽然试图使用前一种方法(直接依赖于定位器),但我更喜欢后者。它感觉更干净。

我想知道这里是否有其他可行的选择。

答案 2 :(得分:2)

  

是否应该传递一个容器

不,因为这会导致Service Locator anti-pattern

  

其中所有实例都可以是RegisterInstance

服务应该在应用程序的启动路径中注册,而不是按类型本身注册。当您有多个应用程序(例如Web应用程序,Web服务,WPF客户端)时,通常会有一个通用的引导程序项目,它将所有服务连接在一起以用于共享层(但每个应用程序仍将具有其独特的连接,因为没有应用程序表现为相同)。

  

一切都应该在启动

中的某处由RegisterType完成

是的,你应该在启动时连接所有东西。

  

如何在需要时使容器可以访问。

你不应该。应用程序应该忽略容器的使用(如果使用任何容器,因为这是可选的)。如果你不这样做,你会做很多事情,比如测试。但是,您可以在应用程序的启动路径中定义的类型中注入容器(例如,Composition Root)。这样,应用程序就可以清楚地了解容器的任何信息。

  

构造函数注入似乎是错误的,作为标准方式的争论

构造函数注入是注入依赖项的首选方式。但是,它可能会将现有的应用程序重构为构造函数注入。在**罕见的情况下,构造函数注入不起作用,您可以恢复属性注入,或者当无法构建完整的对象图时,您可以注入工厂。当工厂实现是组合根的一部分时,您可以让它依赖于容器。

我发现非常有用的模式,可以建立在依赖注入模式和SOLID设计原则之上,是command / handler pattern。我发现这在较小的应用程序中是一种有用的模式,但是当应用程序变大时它会发光,例如企业应用程序。

答案 3 :(得分:1)

  

现在我开始使用高保真系统。还没有插入CD而且没有USB   坚持插入。我现在不需要mp3解码器。

这似乎非常适合 Setter注入(= C#中的属性注入)。使用NeutralDecoder或NullDecoder作为默认值,并在需要时注入Mp3Decoder。您可以手动或使用DI容器和条件/后期绑定来完成。

http://blog.springsource.com/2007/07/11/setter-injection-versus-constructor-injection-and-the-use-of-required/

  

我们通常建议人们使用构造函数注入   所有其他属性的强制性协作者和setter注入。