如果IoC容器不是服务定位器,那么它有什么意义呢?

时间:2014-12-22 09:11:16

标签: .net dependency-injection inversion-of-control ioc-container

我目前正在学习DI和IoC容器,看看我的应用程序是否可以从使用它们中受益 起初我以为我理解他们有什么好处(即以解耦的方式获取接口的实例),但后来我读了一篇关于common mistakes with IoC containers的博客文章。在考虑了这个问题一段时间之后,如果你只是在应用程序的最开始使用容器,那么似乎没有任何好处。

如果我要在当前的应用程序中实现此模式,我最终会替换像

这样的代码
IFoo foo = new Foo();
IBar bar = new Bar(foo);

container.Register<IFoo, Foo>();
container.Register<IBar, Bar>();
bar = container.GetInstance<IBar>();

在我的初始化例程中。

在我看来,这既不会提高可读性也不会解耦,只会使我的代码复杂化。

显然,这不可能是真的,因为否则它不会是如此重要。那我错过了什么?

更新
我真正的问题如下:IoC容器只应在组合根中使用吗?

如果是这种情况我真的不明白这一点,因为我已经有了这样一个函数,其中创建了所有主要对象,并且隐藏new调用的好处似乎非常小。 / p>

此外,我想知道为什么许多DI框架支持范围,当它们只应该在一个地方使用时(而不是在我的业务逻辑中,它们才有意义)?

4 个答案:

答案 0 :(得分:2)

这里的问题是你混淆了两件事。 IoC容器和IoC(或更确切地说,依赖注入)模式。

依赖注入是一种控制反转形式,但所有IoC都不是DI。 DI是一种设计模式,其中依赖项被注入到您的类中,而不是您的类本身创建这些依赖项。

您不需要DI容器来进行DI。虽然一个容器在您的应用程序设计为使用DI时提供了许多不错的功能。所以,是的,DI容器是服务定位器,或者甚至是通用工厂。

当人们说服务位置不好或者说它是反模式时,他们意味着在你的应用程序中使用服务位置模式,你需要一个东西的实例是坏的。这会在具体实例上创建整个应用程序的依赖关系。即使您正在使用带有服务定位器的接口,您仍然会强制组件选择要创建的依赖项。当进行单元测试时,这会产生影响,因为你不能再通过Mock或者#34; Dummy&#34;对象的实例,因为它们现在被硬编码到类实例化中。

DI的观点是,一个类不知道它正在使用的对象的具体实例,所以你可以传递任何东西。这可以是一个真实的实例,一个测试实例,一个特定于用户的实例,一个特定于环境的实例等等......所有对象都知道它有一个接口来调用它来获得它想要的东西。

当然,您可以理解在不必更改对象本身代码的情况下随时更换依赖项是多么方便?

是的,在组合根目录中配置DI可能会增加配置的复杂性,但这是将依赖项隔离到接口中的行为,这使得它们更灵活并减少应用程序中的依赖性。

不同类型的应用与DI有所不同。

除了DI的所有好处之外,DI容器在对象生命周期管理方面提供了巨大的好处。例如,在Web应用程序中,您可以将其配置为每个Web请求甚至每个用户或会话提供唯一的依赖关系实例。并在请求或会话结束后销毁这些实例。您也不必在图表中传递对象。我们假设您使用对象图顶部的对象,并再次需要10次调用。您不需要将其全局化,或者将其传递给所有不需要它的对象。

DI是您最初必须信任的事情之一,您使用它越多,您就越能够欣赏它。

那么是什么让使用DI容器&#34;好&#34;是你正在做你所有的服务地点&#34;在一个地方。然后,您正在构建对象图,然后通过注入使用它。您无法摆脱必须创建依赖关系的事实,以及它是否通过服务地点或直接启动&#34; new&#34;那些依赖必须存在。但是通过将它们放在一个地方,您可以降低应用程序其余部分的复杂性。

这是一款贝壳游戏吗?在某种方式。但好处是非常真实的。

编辑:

想象一下:

public class Foo : IFoo {
    public Foo(IBar bar) {}
}

public class Bar : IBar {
    public Bar(IBaz baz) {}
}

public class Baz : IBaz {
    public Baz(IFooBar fooBar) {}
}

.... etc..

现在,如果你想创建一个新的IFoo,你必须这样做:

IFoo = new Foo(new Bar(new Baz(new FooBar(... etc...))));

而且,这可能会变得非常复杂。但是,使用DI容器,您只需要注册所有接口,DI容器现在将创建所有其他实例。

IFoo = kernel.Get<IFoo>(); // or whatever your DI container uses to get the service

它会自动创建所有相关实例,因为您已使用您已配置的给定生活方式对其进行配置(单例,按请求,瞬态,自定义等)。因此,如果将IBaz配置为单例,那么它将创建Foo和Bar的新实例,但在任何需要的地方重用相同的Baz实例。

正如我所说,DI容器不是DI所必需的,但它们确实提供了许多非常好的功能,你会越来越喜欢使用它。

答案 1 :(得分:1)

与所有工具和模式一样,最好在需要时使用它们。 DI模式很棒,因为它使单元测试更容易,因为它不依赖于您依赖于接口的特定类。当单元测试类时,注入的接口可以用模拟对象替换,该模拟对象的行为完全符合要求。

IoC容器用于对象创建管理。与DI模式一起,您可以在一个位置指定对象的创建方式。如果您稍后更改对象的创建方式,那么在整个代码中创建对象的相反方法可能会很麻烦。然后,您需要前往每个位置进行更改。使用IoC容器时,只需在一个地方进行更改。

除此之外,如果您按照所有类的DI模式,使用IoC容器GetInstance将级联创建特定任务所需的所有对象,并且只需要一个GetInstance调用。

答案 2 :(得分:0)

容器是一个服务定位器,正如您在示例中所示。

但是,许多应用程序通常使用ASP.NET MVC等框架来隐藏服务位置。相反,您在类中使用构造函数注入来注入它们的依赖项。

如果您不使用库/框架,通常只是在项目中使用根类的服务位置。所有其他类都通过构造函数注入来获取它们的依赖项。

由于你的例子非常简单,所以没有说明这一点。所以是的,一个最小的例子确实变得更复杂,而真实世界的应用程序变得更少复杂。

您也忘记了IoC容器也可以处理不同的生命周期。在HTTP应用程序中,这意味着许多请求可以并行运行而不会出现线程问题,因为每个请求都有自己的容器范围。这太复杂了。

答案 3 :(得分:0)

由于好莱坞原则,有了服务定位器,一个类要求另一个类的实例,并且可能还要求其他类的实例,程序员可能会被诱惑要求实例立即节省时间或只是因为没有看到潜在的问题。后来维护者必须调试奇怪的行为,因为无法想象一个类正在处理某些实例(与使用全局变量相同但更微妙和更难找到)。

  

不要打电话给我们......

IoC容器会创建对象,将正确的实例传递给对象构造函数

  

......我们会打电话给你

实际上,它是创建实例并将其提供给类的容器,而不是调用任何方法来执行此操作的类,这可以减轻类的至少1个响应性(以某种方式获取实例)。

这与实例得到的“方式”分离。在围绕force传递实例以编写额外代码的复杂项目中(只看到像Irrlicht这样的某些开源3d引擎的实现)很快就会变得一团糟,IoC容器将该代码带走一次并永远降低复杂性。

请注意,对于服务定位器,该类必须至少调用1“getInstance”方法。使用IoC容器,您也可以摆脱该调用。