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

Nia*_*ton 145 dependency-injection inversion-of-control

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

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

sle*_*ske 124

这得看情况 :-).

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

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

  • 我认为在很多情况下最好使用Null Object模式并坚持要求构造函数上的引用.这避免了所有空检查和增加的圈复杂度. (10认同)
  • @Mark:好点.但是,问题是关于向现有类添加依赖项.然后保持no-arg构造函数允许向后兼容. (3认同)

Sco*_*ott 20

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

此外,为了防止自己不得不改变旧构造函数的每次使用,只需将构造函数链接应用为旧构造函数和新构造函数之间的临时桥接.

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Joe*_*Joe 17

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

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


epi*_*tka 11

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


Phi*_*ppe 11

一般优选的方法是尽可能多地使用构造器注入.

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

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

当你在他的书".NET中的依赖注入"中使用Mark Seemann所称的本地默认值时,你可以使用属性注入:依赖是可选的,因为你可以提供一个很好的工作实现但是想让调用者指定一个不同的实现需要.

(以下的答案)


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

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


nkr*_*1pt 7

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

只要类接口(API)在执行其任务所需的内容中清楚,就可以了.

  • @ nkr1pt:大多数人(包括我在内)同意如果注入没有完成,它允许你创建一个在运行时失败的类,那么setter注入就会很糟糕.我相信有人反对你的个人品味声明. (10认同)
  • 是的,有很多争论的构造函数都很糟糕.这就是为什么你重构具有许多构造函数参数的类:-). (3认同)

Rus*_*Cam 7

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


cta*_*cke 6

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

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


Dav*_*iff 6

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

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

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