关于控制反转:通过构造函数或属性设置者进行依赖注入?

Dependency injection through constructors or property setters?

我正在重构一个类并向它添加一个新的依赖项。类当前正在构造函数中获取其现有依赖项。为了一致性,我将参数添加到构造函数中。当然,还有一些子类,甚至更多用于单元测试,所以现在我正在玩一个游戏,改变所有的构造器来匹配,这需要很多时间。它使我认为,将属性与setter一起使用是获取依赖性的更好方法。我不认为注入的依赖项应该是构建类实例的接口的一部分。您添加了一个依赖项,现在您的所有用户(子类和直接实例化您的任何人)都突然知道了它。这就像是一个封装的突破。

这似乎不是现有代码的模式,所以我想找出一般的共识是什么,构造函数和属性的优缺点。使用属性设置器更好吗?


嗯,这取决于:—)。

如果没有依赖项,类就不能完成它的工作,那么将它添加到构造函数中。这个类需要新的依赖关系,所以您希望您的更改能够打破这种依赖关系。另外,创建一个没有完全初始化的类("两步构造")是一个反模式(imho)。

如果类可以在没有依赖项的情况下工作,那么setter就可以了。


类的用户应该知道给定类的依赖关系。如果我有一个类,例如,连接到一个数据库,并且没有提供注入持久层依赖性的方法,那么用户将永远不会知道到数据库的连接必须是可用的。但是,如果我更改了构造函数,我会让用户知道持久性层有依赖性。

另外,为了防止您不得不改变对旧构造函数的每次使用,只需将构造函数链接作为新旧构造函数之间的临时桥梁。

1
2
3
4
5
6
7
8
9
10
11
public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

依赖注入的一个要点是揭示类具有哪些依赖性。如果类有太多的依赖项,那么可能需要进行一些重构:类的每个方法都使用所有依赖项吗?如果没有,那么这是一个很好的起点,看看在哪里可以将类拆分。


当然,使用构造函数意味着您可以同时验证所有内容。如果您将事物分配到只读字段中,那么您就可以从构建时起保证对象的依赖性。

添加新的依赖关系是一件很痛苦的事情,但至少这样编译器会一直抱怨直到正确为止。我想这是件好事。


一般的首选方法是尽可能多地使用构造函数注入。

构造函数注入准确地说明了对象正常运行所需的依赖项-没有什么比更新对象和在调用方法时使其崩溃更烦人的了,因为没有设置某些依赖项。构造函数返回的对象应处于工作状态。

尽量只使用一个构造函数,这样可以保持设计的简单性并避免歧义(如果不是针对人类,则针对DI容器)。

当Mark Seemann在他的书《.NET中的依赖项注入》中调用本地默认值时,可以使用属性注入:依赖项是可选的,因为您可以提供一个良好的工作实现,但如果需要,希望允许调用方指定另一个。

(以前的回答如下)

如果强制注入,我认为构造函数注入更好。如果这添加了太多的构造函数,请考虑使用工厂而不是构造函数。

如果注射是可选的,或者如果你想改变它的半程槽,设定注射是很好的。我一般不喜欢二传手,但这是一个品味问题。


如果您有大量的可选依赖项(这已经是一种味道),那么可能需要进行setter注入。不过,构造函数注入更好地揭示了您的依赖性。


我个人更喜欢提取和重写"模式",而不是在构造函数中注入依赖项,这主要是由于您问题中概述的原因。可以将属性设置为virtual,然后重写派生可测试类中的实现。


这在很大程度上是个人品味的问题。我个人更喜欢setter注入,因为我相信它在运行时替代实现的方式上给了您更多的灵活性。此外,在我看来,具有大量参数的构造函数并不干净,构造函数中提供的参数应限于非可选参数。

只要类接口(API)明确了执行其任务所需的内容,你很好。


我执行构造函数注入,因为这看起来最合乎逻辑。就像是说我的类需要这些依赖项来完成它的工作。如果它是可选的依赖项,那么属性似乎是合理的。

我还使用属性注入来设置容器没有引用的内容,例如使用容器创建的演示者上的ASP.NET视图。

我认为它不会破坏封装。内部工作应保持在内部,依赖关系处理不同的问题。


我更喜欢构造函数注入,因为它有助于"强制"类的依赖性需求。如果它在目录中,消费者必须设置对象才能让应用程序编译。如果您使用setter注入,他们可能直到运行时才知道他们有问题——根据对象的不同,可能是运行时太晚了。

我仍然不时地使用setter注入,当注入的对象可能需要一系列的工作时,比如初始化。


我最近遇到了一种情况,在一个类中有多个依赖项,但是在每个实现中只有一个依赖项必须要更改。由于数据访问和错误日志记录依赖项可能只为测试目的而更改,因此我为这些依赖项添加了可选参数,并在构造函数代码中提供了这些依赖项的默认实现。通过这种方式,类保持其默认行为,除非被类的使用者重写。

使用可选参数只能在支持它们的框架中完成,例如.NET 4(对于C和VB.NET,尽管VB.NET一直都有它们)。当然,您可以通过简单地使用一个可以由类的使用者重新分配的属性来完成类似的功能,但是您不能通过将私有接口对象分配给构造函数的参数来获得不可变的优势。

所有这些都是说,如果您引入了一个必须由每个使用者提供的新依赖项,那么您将不得不重构构造函数和使用类的所有代码。我上面的建议只适用于您能够为所有当前代码提供默认实现,但仍然能够在必要时覆盖默认实现的情况。


一个值得考虑的选项是从简单的单个依赖项中组合复杂的多个依赖项。也就是说,为复合依赖项定义额外的类。这使得WRT构造函数注入变得更加容易——每次调用的参数更少——同时仍然维护必须提供所有依赖项来实例化对象。

当然,如果存在某种逻辑上的依赖项分组,那么这是最有意义的,因此复合不仅仅是一个任意的聚合,而且如果一个复合依赖项有多个依赖项,那么这也是最有意义的——但是参数块"pattern"已经存在很长时间了,我所看到的大多数依赖项都相当不错。任意的

但就个人而言,我更喜欢使用方法/属性设置器来指定依赖项、选项等。调用名有助于描述正在发生的事情。不过,最好提供一个示例,这就是如何设置代码片段,并确保依赖类执行足够的错误检查。您可能需要为设置使用有限状态模型。


这是一篇旧文章,但如果将来需要,这可能有任何用处:

网址:https://github.com/omegamit6zeichen/prinjet

我有一个类似的想法,并提出了这个框架。它可能还远未完成,但它是一个侧重于属性注入的框架的想法


构造器注入确实显式地揭示了依赖性,如果在构造器中签入参数,代码更易于阅读,也更不容易出现未处理的运行时错误,但这确实取决于个人意见,而且使用DI的次数越多,根据项目的不同,您就越容易来回摇摆。我个人对代码有一种类似于带有一长串参数的构造函数的问题,我认为对象的使用者应该知道依赖项,以便无论如何使用该对象,所以这就是使用属性注入的理由。我不喜欢属性注入的隐式本质,但我发现它更优雅,从而使代码看起来更干净。但另一方面,构造函数注入确实提供了更高程度的封装,根据我的经验,我尽量避免使用默认的构造函数,因为如果不小心,它们可能会对封装数据的完整性产生不良影响。

根据特定的场景,明智地选择按构造函数或按属性的注入。不要因为有必要使用DI就觉得必须使用它,这样可以防止糟糕的设计和代码味道。有时,如果一个模式的努力和复杂性超过了好处,那么使用它就不值得。保持简单。


这取决于您希望如何实现。我更喜欢构造函数注入,只要我觉得进入实现的值不会经常改变。如果Company Stragtegy与Oracle服务器一起使用,我将通过构造函数注入为bean实现连接配置我的datsource值。否则,如果我的应用程序是一个产品,并且有机会连接到客户的任何数据库,我将通过setter注入实现这种数据库配置和多品牌实现。我刚刚举了一个例子,但是有更好的方法来实现我上面提到的场景。