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

Dependency injection 通过构造函数或属性设置器进行依赖注入?,dependency-injection,inversion-of-control,Dependency Injection,Inversion Of Control,我正在重构一个类并向其添加一个新的依赖项。该类当前正在构造函数中获取其现有依赖项。为了保持一致性,我将参数添加到构造函数中。 当然,有几个子类,还有更多的子类用于单元测试,所以现在我正在玩一个游戏,改变所有的构造函数以匹配,这需要花费很多时间。 这让我认为,将属性与setter一起使用是获得依赖关系的更好方法。我不认为注入的依赖项应该是构建类实例的接口的一部分。您添加了一个依赖项,现在您的所有用户子类和任何直接实例化您的人突然知道了它。这感觉像是封装的中断 这似乎不是这里现有代码的模式,所以我希

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


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

这主要是个人喜好的问题。 就个人而言,我倾向于选择setter注入,因为我相信它提供了更大的灵活性,可以在运行时替换实现。 此外,在我看来,具有大量参数的构造函数并不干净,并且构造函数中提供的参数应限于非可选参数

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

我更喜欢构造函数注入,因为它有助于强制执行类的依赖性要求。如果它在c'tor中,消费者必须设置对象以使应用程序编译。如果您使用setter注入,则在运行时之前,他们可能不知道自己有问题,并且根据对象的不同,可能会在运行时出现问题


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

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

好吧,这取决于:-

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


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

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


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

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

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


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

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

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

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

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

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

前答案如下


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

如果注入是可选的,或者如果您想中途更改,setter注入是很好的。我通常不喜欢setter,但这是一个品味问题。

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

此外,为了避免您必须更改旧构造函数的每次使用,只需将构造函数链接作为新旧构造函数之间的临时桥梁

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构造函数注入变得更容易一些——每次调用的参数更少——同时仍然保持必须提供所有依赖项才能实例化

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

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

我最近在一个类中有多个依赖项,但在每个实现中只有一个依赖项需要更改。由于数据访问和错误日志依赖关系可能仅为测试目的而更改,因此我为这些依赖关系添加了可选参数,并在构造函数代码中提供了这些依赖关系的默认实现。这样,除非被类的使用者重写,否则类将保持其默认行为

使用可选参数只能在支持这些参数的框架中完成,例如用于C和VB.NET的.NET4,尽管VB.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.
    }
}

综上所述,如果您要引入一个必须由每个使用者提供的新依赖项,那么您必须重构您的构造函数和所有使用您的类的代码。只有当您能够为所有当前代码提供默认实现,但仍然能够在必要时覆盖默认实现时,我的上述建议才真正适用。

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

构造函数注入确实明确地揭示了依赖关系,如果在构造函数中检查参数,代码更可读,更不容易出现未经处理的运行时错误,但这确实取决于个人意见,你使用DI越多,你就越倾向于以一种或另一种方式来回摇摆,这取决于项目。我个人觉得代码闻起来像是一长串参数的构造函数,我觉得一个对象的使用者应该知道依赖关系,以便使用该对象,所以这就需要使用属性注入。我不喜欢属性注入的隐式本质,但我发现它更优雅,导致代码看起来更干净。但另一方面,构造函数注入确实提供了更高程度的封装,根据我的经验,我尽量避免使用默认构造函数,因为如果不小心,它们可能会对封装数据的完整性产生不良影响

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

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



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

请指定为什么要进行向下投票?是的,具有大量参数的构造函数是不好的。这就是为什么要用大量构造函数参数重构类:-.@nkr1pt:包括我在内的大多数人都同意,如果setter注入允许您创建一个在运行时失败的类(如果注入没有完成),那么setter注入是不好的。我相信有人因此反对你的说法,认为这是个人喜好。当然,构造函数链接只有在新参数有合理的默认值时才有效。但是,无论如何,您都无法避免破坏东西……通常,您会使用依赖项注入之前在方法中使用的任何东西作为默认参数。理想情况下,这将使新的构造函数添加成为一个干净的重构,因为类的行为不会改变。我认为我的例子中的问题是,我正在添加依赖项的类有几个子类。在IOC容器世界中,属性将由容器设置,使用setter至少可以减轻所有子类之间构造函数接口复制的压力。我认为,通常中途更改注入是不好的样式,因为您正在向对象添加隐藏状态。但没有例外,当然…是的,这就是为什么我说我不太喜欢塞特。。。我喜欢构造函数方法,因为它不能更改。如果添加了太多的构造函数,请考虑使用工厂而不是构造函数。您基本上会推迟任何运行时异常,甚至可能出现错误,最终会被服务定位器实现卡住。@Marco这是我以前的答案,您是对的。如果有很多构造函数,我会认为这个类做了太多的事情:或者考虑一个抽象的工厂。谢谢你的回答。显然,构造函数似乎是流行的答案。然而,我确实认为它在某种程度上破坏了封装。在依赖注入之前,该类将声明并实例化完成其工作所需的任何具体类型。有了DI,子类和任何手动实例化器现在都知道基类正在使用什么工具。您添加了一个新的依赖项,现在您必须从所有子类链接实例,即使它们本身不需要使用依赖项。刚刚写了一个很长的答案,但由于此站点上的异常而丢失了它!!:总之,基类通常用于重用逻辑。这个逻辑很容易进入子类。。。所以您可以考虑基类和子类=一个关注点,它依赖于多个执行不同任务的外部对象。事实上,您有依赖项,并不意味着您需要公开任何您以前保持私有的内容。我认为在许多情况下,最好使用Null对象模式并坚持要求构造函数上的引用。这避免了所有的空检查和增加的圈复杂度。@马克:好的。然而,问题是如何向现有类添加依赖项。然后保留一个无参数构造函数允许向后兼容。当依赖项需要运行时,但默认注入该依赖项通常就足够了。那么,该依赖项应该被属性或构造函数重载覆盖吗?@Patrick:由于类不能在没有依赖项的情况下完成其工作,我的意思是没有合理的默认值,例如,该类需要DB连接。在你的情况下,两者都会起作用。我通常还是会选择构造函数方法,因为它降低了复杂性,如果setter被调用两次呢?在这里写得不错,我认为模式的正式名称是模板方法。即使在内部编码时,我总是从这样一个角度来编写代码,即我是一个独立的承包商,负责开发可再发行的代码。我认为它将是开源的。通过这种方式,我确保代码是模块化的、可插拔的,并且遵循坚实的原则。此外,这极大地减少了循环依赖的危险。。。