3

想象一下,您有一个包含数百个类的应用程序,这些类实现了数十个“高级”接口(即组件级)。是否有推荐的依赖注入方式(如统一)。是否应该有一个可用于引导的“通用容器”,可以作为单例访问?是否应该传递一个容器,所有实例都可以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

4 回答 4

6

首先,依赖注入是一种不需要容器的设计模式。DI 模式指出:

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

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

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

我不喜欢每次都必须在容器中注册每个组件的想法,我只是讨厌这样。有几个工具可以帮助您根据约定自动注册组件,我不使用 Unity,但我可以为您指出例如NinjectAutoFac

我实际上正在编写一个小实用程序来根据约定使用几乎任何 DI 工具自动注册组件,它仍处于开发阶段

关于您的问题:

是否应该有一个可用于引导的“通用容器”,可以作为单例访问?

答案是肯定的,(有一个工具可以抽象出所使用的 DI 工具,称为ServiceLocator)这就是 DI 工具的工作原理,有一个静态容器可供应用程序使用,但是不建议在域对象中使用它创建实例,这被认为是一种反模式

顺便说一句,我发现这个工具对于在运行时注册组件非常有用:

http://bootstrapper.codeplex.com/

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

不,那会违反得墨忒耳法则。如果您决定使用容器,最好在应用程序启动时注册组件

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

使用 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

于 2012-06-12T21:45:47.797 回答
2

首先,有Composition Root模式,你尽快建立你的依赖,Main()在桌面,global.asax/app_start在web。然后,依赖构造函数注入,因为这使您的依赖关系清晰。

但是,您仍然需要一些东西来真正解决依赖关系。我知道至少有两种方法。

第一个在于使用服务定位器(这几乎等于使容器成为单例)。对于很多人来说,这被认为是一种反模式,但它确实很好用。每当您需要服务时,您都可以向容器索取它:

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

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

另一种方法是依赖对象工厂作为单例提供,并将容器注入其中:

 var service = IMyServiceFactory.Instance.CreateService();

诀窍是工厂的实现在内部使用容器来解析服务。但是,这种方法带有一个额外的工厂层,使您的业务代码独立并且不知道IoC!您可以自由地完全重新设计工厂,使其内部不使用IoC,并且仍然维护每一行业务代码。

换句话说,将容器/定位器隐藏在工厂层后面只是一个技巧。

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

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

于 2012-06-12T22:00:03.003 回答
2

是否应该传递容器

不,因为这会导致Service Locator 反模式

所有实例都可以 RegisterInstance 的地方

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

是否应该在启动时的某个地方由 RegisterType 完成所有操作

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

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

你不应该。应用程序应该忽略容器的使用(如果使用了任何容器,因为这是可选的)。如果你不这样做,你会让很多事情变得更加困难,比如测试。但是,您可以将容器注入应用程序启动路径中定义的类型(也称为Composition Root)。这样,应用程序就不会知道有关容器的任何信息。

构造函数注入似乎是错误的,有争议的是标准方式

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

我发现一个非常有用的模式,可以建立在依赖注入模式和SOLID设计原则之上,是命令/处理程序模式。我发现这在较小的应用程序中很有用,但当应用程序变大时它会大放异彩,例如企业应用程序。

于 2012-06-13T08:44:44.707 回答
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 注入。

于 2012-06-13T09:38:57.447 回答