通过构造函数或属性设置器进行依赖注入?

时间:2009-10-01 12:10:50

标签: dependency-injection inversion-of-control

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

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

14 个答案:

答案 0 :(得分:123)

嗯,这取决于: - )。

如果没有依赖项,类无法完成其工作,则将其添加到构造函数中。类需要新的依赖项,因此您希望您的更改能够破坏事物。此外,创建一个未完全初始化的类(“两步构造”)是一种反模式(恕我直言)。

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

答案 1 :(得分:19)

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

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

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

依赖注入的一个要点是揭示该类具有哪些依赖关系。如果该类具有太多依赖关系,那么可能需要进行一些重构:该类的每个方法是否都使用所有依赖关系?如果没有,那么这是一个很好的起点,可以看出这个课程可以拆分的地方。

答案 2 :(得分:17)

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

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

答案 3 :(得分:11)

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

答案 4 :(得分:11)

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

构造函数注入准确地说明了对象正常运行所需的依赖关系 - 没有什么比新建一个对象更令人讨厌,并且在调用一个方法时因为没有设置某个依赖项而让它崩溃。构造函数返回的对象应处于工作状态。

尝试只有一个构造函数,它保持设计简单并避免歧义(如果不是人类,对于DI容器)。

当你在他的书“.NET中的依赖注入”中有Mark Seemann所谓的本地默认时,你可以使用属性注入:依赖是可选的,因为你可以提供一个很好的工作实现但是想要允许呼叫者在需要时指定不同的一个。

(以下的回答)


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

如果注射是可选的,或者如果你想在半途中改变它,那么setter注射就很好。我一般不喜欢制定者,但这是一个品味问题。

答案 5 :(得分:7)

这主要取决于个人品味。 我个人倾向于选择setter注入,因为我相信它可以让你在运行时替换实现的方式更灵活。 此外,在我看来,具有大量参数的构造函数并不干净,构造函数中提供的参数应限于非可选参数。

只要类接口(API)清楚地执行其任务所需的内容, 你很好。

答案 6 :(得分:7)

我个人更喜欢 Extract and Override “模式”而不是在构造函数中注入依赖项,主要是出于问题中概述的原因。您可以将属性设置为virtual,然后覆盖派生的可测试类中的实现。

答案 7 :(得分:6)

我更喜欢构造函数注入,因为它有助于“强制”类的依赖性要求。如果它在c'tor中,则消费者具有来设置对象以使应用程序编译。如果使用setter注入,他们可能不知道他们在运行时有问题 - 并且根据对象,它可能在运行时间较晚。

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

答案 8 :(得分:6)

我更喜欢构造函数注入,因为这似乎是最合乎逻辑的。就像说我的班级要求这些依赖关系来完成它的工作。如果它是一个可选的依赖项,那么属性似乎是合理的。

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

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

答案 9 :(得分:3)

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

当然,如果存在某种依赖关系的逻辑分组,则最有意义,因此复合不仅仅是一个任意聚合,如果单个复合依赖关系存在多个依赖关系,则最有意义 - 但参数块“模式“已经存在了很长时间,而且我见过的大多数都是非常武断的。

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

答案 10 :(得分:3)

我最近ran into a situation我在一个类中有多个依赖项,但每个实现中只有一个依赖项必然会发生变化。由于数据访问和错误日​​志记录依赖项可能只是为了测试目的而更改,因此我为这些依赖项添加了可选参数,并在我的构造函数代码中提供了这些依赖项的默认实现。这样,除非被类的使用者覆盖,否则该类将保持其默认行为。

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

所有这些都说,如果你要引入一个必须由每个消费者提供的新依赖项,你将不得不重构你的构造函数以及所有类消费者的代码。我的上述建议实际上只适用于您能够为所有当前代码提供默认实现,但仍然可以在必要时覆盖默认实现的能力。

答案 11 :(得分:1)

这是一篇旧帖子,但如果将来需要它可能会有用:

https://github.com/omegamit6zeichen/prinject

我有类似的想法,想出了这个框架。它可能远非完整,但它是一个关注属性注入的框架的想法

答案 12 :(得分:0)

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

答案 13 :(得分:0)

构造函数注入确实显示了依赖关系,使得代码更具可读性,并且如果在构造函数中检查了参数,则不太容易出现未处理的运行时错误,但它确实归结为个人观点,并且您使用DI的次数越多根据项目的不同,你会倾向于以某种方式来回摇摆。我个人有代码味道的问题,比如带有一长串参数的构造函数,我觉得对象的使用者应该知道依赖关系才能使用该对象,所以这就是使用属性注入的情况。我不喜欢属性注入的隐含性质,但我发现它更优雅,导致代码更清晰。但另一方面,构造函数注入确实提供了更高程度的封装,根据我的经验,我尝试避免使用默认构造函数,因为如果不小心它们会对封装数据的完整性产生不良影响。

根据您的具体方案,根据构造函数或属性明智地选择注入。并且不要觉得你必须使用DI只是因为它似乎是必要的,它将防止糟糕的设计和代码味道。如果努力和复杂性超过收益,有时使用模式是不值得的。保持简单。