依赖注入是通过构造函数还是属性设置函数?

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

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

35169 次浏览

这很大程度上取决于个人品味。 就我个人而言,我倾向于使用 setter 注入,因为我相信它在运行时替代实现的方式上提供了更大的灵活性。 此外,在我看来,带有大量参数的构造函数并不干净,构造函数中提供的参数应该仅限于非可选参数。

只要类接口(API)清楚它需要什么来执行它的任务, 你真厉害。

我更喜欢构造函数注入,因为它有助于“强制”类的依赖需求。如果它在 c‘ tor 中,则由消费者 已经设置对象以使应用程序进行编译。如果使用 setter 注入,那么在运行时之前他们可能不知道自己有问题——根据对象的不同,运行时可能会晚一些。

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

如果您有大量可选的依赖项(这已经是一种味道) ,那么 setter 注入可能是一种方法。不过,构造函数注入可以更好地揭示您的依赖关系。

嗯,这要看情况: ——)。

如果类在没有依赖项的情况下无法完成其工作,那么将其添加到构造函数中。类 需求的新依赖项,所以您希望您的更改打破这些依赖项。另外,创建一个没有完全初始化的类(“两步构造”)是一个反模式(IMHO)。

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

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

添加新的依赖项是一件非常痛苦的事情,但是至少这样编译器会一直抱怨,直到它正确为止。我觉得这是好事。

我更喜欢构造函数注入,因为这似乎最符合逻辑。这就像说我的类 需要这些依赖性做它的工作。如果它是一个可选的依赖项,那么属性似乎是合理的。

我还使用属性注入来设置容器没有引用的内容,比如使用容器创建的演示文稿上的 ASP.NET View。

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

我个人比较喜欢 撤离并重写“模式”而不是在构造函数中注入依赖项,这主要是因为您的问题中概述的原因。可以将属性设置为 virtual,然后在派生的可测试类中重写实现。

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

构造函数注入确切地说明了对象正常运行所需的依赖项是什么——没有什么比新建一个对象并在调用该对象的方法时因为某些依赖项没有设置而导致它崩溃更让人恼火的了。构造函数返回的对象应处于工作状态。

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

你可以使用属性注入当你有一个马克西曼在他的书中称为 本地默认值的依赖注入。NET”: 依赖项是可选的,因为您可以提供一个良好的工作实现,但是希望允许调用方在需要时指定一个不同的实现。

(以下是以前的答案)


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

如果注射是可选的,或者如果你想改变它的中途槽塞特注射是很好的。我一般不喜欢长毛猎犬,但这是品味的问题。

类的用户是 假设,以了解给定类的依赖关系。例如,如果我有一个连接到数据库的类,并且没有提供注入持久层依赖关系的方法,用户将永远不会知道到数据库的连接必须是可用的。但是,如果我修改构造函数,我会让用户知道存在对持久层的依赖。

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

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.
}
}

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

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

当然,如果存在某种逻辑上的依赖关系分组,那么最有意义的就是它,因此复合不仅仅是一个任意的聚合,如果一个复合依赖关系存在多个依赖关系,那么最有意义的就是它——但是参数块“模式”已经存在很长时间了,而且我看到的大多数都是相当任意的。

但就我个人而言,我更喜欢使用方法/属性设置器来指定依赖项、选项等。这些称呼有助于描述正在发生的事情。不过,提供 this-is-how-to-set-It-up 示例代码片段是一个好主意,并确保依赖类执行足够的错误检查。您可能需要使用有限状态模型进行设置。

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

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

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

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

构造函数注入确实显式地揭示了依赖关系,如果在构造函数中检查参数,代码更具可读性,并且不容易出现未处理的运行时错误,但它确实归结为个人观点,而且越多地使用 DI,你就越倾向于根据项目的不同方式来回摇摆。我个人对于代码有一些问题,就像构造函数有一长串参数,我觉得对象的使用者应该知道依赖关系,这样才能使用对象,所以这就为使用属性注入提供了理由。我不喜欢属性注入的隐式特性,但是我发现它更优雅,因此代码看起来更干净。但另一方面,构造函数注入确实提供了更高程度的封装,而且根据我的经验,我尽量避免使用缺省构造函数,因为如果不小心的话,它们可能会对封装数据的完整性产生不良影响。

根据您的具体场景,明智地选择由构造函数或属性进行注入。并且不要认为你必须使用依赖注入只是因为它似乎是必要的,它将防止糟糕的设计和代码气味。有时候,如果努力和复杂性超过了收益,那么使用模式就不值得了。简单点。

这是一篇老文章,但如果将来需要的话,这篇文章可能有用:

Https://github.com/omegamit6zeichen/prinject

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

何时使用构造函数注入?
当我们希望确保创建的对象具有所有的依赖项,并确保所需的依赖项不为空时。

何时使用塞特注射?
当我们使用可选的依赖项时,这些依赖项可以在类中分配合理的默认值。否则,非空检查必须在代码使用依赖项的任何地方执行。 此外,setter 方法使该类的对象可以在以后重新配置或重新注入。

资料来源: Spring 文档 , 重温 Java