构造函数中的依赖注入容器

4
为什么在构造函数中放置一个容器是不好的?例如,您想要在另一个类(C)的构造函数中解析类B,因为您需要使用已解析的依赖项使用类(B)(您开始使用类C时,就像使用已解决的B一样)。
1个回答

11
为什么在构造函数中放置容器是不好的?
我想你的意思是将容器作为构造函数参数传递。这实际上是服务定位器模式的一种变体,在此上下文中被认为是反模式。有几个原因你可能不想这样做。
首先,你的类的用户只会知道该类需要一个容器来解决其依赖关系。这些信息量等于没有信息,因为你仍然不知道该类将依赖于什么。你想为该类编写单元测试吗?你必须查看类的内部并查看它正在解析哪些类型,对它们进行模拟并为每个测试初始化容器。这也意味着对某些代码的更改将使其编译,但可能会破坏某些测试:例如,当新代码依赖于尚未在容器中注册的类时就是这种情况。
使用服务定位器的常见副作用是,您在请求依赖项时无法确定是否会在运行时遇到异常。每个类都注册正确吗?尽管某些容器提供了检查每个接口是否已注册的可能性,但这并不意味着它已注册到正确的类型。例如,可能会发生一个类型被注册两次,具有两个不同的实现,并且如果任何代码片段调用容器,则很难注意到。
更好的解决方案是组合根模式本博客文章还解释了为什么服务定位器可能不是一个好主意。

针对最新进展进行编辑:

显然,您正在使用一个依赖于您的类具有默认构造函数的第三方库。让我们假设您没有办法影响您的类的实例化,并且您必须让该框架完成其工作。请注意,这可能是一个很大的假设,请首先调查第三方库是否有可能实现。乍一看,像ASP.NET WebForms和WCF这样的框架并没有给你很多机会,但是在这些情况下有方法可以减轻痛苦。

我的意思只是在构造函数中创建容器,向容器添加相应的依赖项并解析对象,这可以通过简单地创建依赖项对象的实例并使用它来创建相关对象来完成。

我可能漏掉了什么,但是为什么需要在构造函数中注册依赖项?难道您不能在构造函数中解析它,但在其他地方注册它吗?这仍然是服务定位器,但至少您会正确地做错事情。

为什么在构造函数中这样做是一个坏主意,在其他地方这样做就没问题了?

在任何地方这样做都是一个坏主意,除了一个地方。为什么要将容器注册分散到各个地方呢?如果你真的感觉需要在运行时决定接口的实现,可以使用像工厂一样的东西。
那么,为什么这样做是不好的呢?
- 客户端类依赖于实现和接口,这并不比在构造函数中新建具体类更好。 - 客户端类现在也依赖于容器,并且存在服务定位器的问题(见上文),所以现在这种方法比新建具体类更糟糕。
正如@Steven所说,您失去了所有依赖注入的优势。真正的根本问题是:为什么您绝对想在这个地方进行DI?您想使用这种方法的哪些优点?根据答案,可能会有几种解决方案。我脑海中有两个例子: 解决方案1:放弃对由第三方库实例化的类进行DI。 解决方案2:使用混合注入+服务定位器的组合。在这种情况下,两个错误可能会变成正确。
public class MyClass
{
    public MyClass()
        : this(Container.Resolve<IDependency>())
    {
    }

    public MyClass(IDependency dep)
    {
    }
}

在这种情况下,您没有在构造函数中使用非本地依赖项,因为它由服务定位器解析,因此您不依赖于实现。

我的意思只是在构造函数中创建容器,添加相应的依赖项到容器并解析对象,这可以通过简单地创建依赖项对象的实例并使用它来创建相关对象来完成。在这种情况下,这很重要,因为存在链式依赖关系,手动创建每个属于该链的对象,并将其作为构造函数参数传递给每个连续的依赖对象可能会很繁琐。至于服务定位器-在这种情况下它是已知的反模式,但这不是我的问题。 - Ivana Stoilova
1
在每个类中创建和配置一个容器是疯狂的。这迫使类再次完全控制其依赖项,与仅在构造函数内部新建依赖项相同。您失去了所有依赖注入的好处,并使事情比“旧方法”更加复杂,因为新建将比配置创建这些实例的容器更容易理解。 - Steven
存在链式依赖,需要手动创建每个属于链的对象并将其作为构造函数参数传递。请开始一个新问题并更详细地解释这一点,并解释为什么您无法使构造函数注入起作用。如果您认为无法使用构造函数注入,则我希望您的设计存在缺陷。为了能够帮助您,您必须解释清楚这一点。 - Steven
1
@IvanaStoilova:你试图解决什么问题?听起来你正在毫无理由地让自己变得非常困难。Steven说得很对。 - Filippo Pensalfini
问题在于我们即将使用一个第三方软件,该软件可能依赖于仅使用无参数(默认)构造函数的对象。我们有一些数据对象是依赖对象(我不同意这种做法),现在我们需要在将它们传递给第三方代码之前解决它们。因此,对于应用程序的其余部分,DI 是可以的,但对于特定的单个情况,我们需要解决这些对象。为什么在构造函数中这样做是一个坏主意,而在其他地方这样做是可以的呢? - Ivana Stoilova
谢谢解释,非常有帮助(尽管问题在最好的情况下有点奇怪)。我也想投票支持答案,但我还不能。 - Ivana Stoilova

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接