何时使用属性注入?

Hop*_*ess 11 dependency-injection ioc-container

  1. 什么时候应该使用属性注入?
  2. 如果完全控制实例创建,我应该默认使用构造函数注入吗?
  3. 我是否正确使用构造函数注入我编写与容器无关的代码?

Ste*_*ven 14

什么时候应该使用属性注入?

如果依赖项是真正可选的,有本地默认值,或者对象图包含循环依赖项,则应使用属性注入.

但是,Property Injection会导致时间耦合,在编写业务线应用程序时,您的依赖项永远不应该是可选的:您应该应用Null对象模式.

你也不应该使用Local Default,因为这会使测试复杂化,隐藏依赖关系,并且很容易忘记配置依赖项.

对象图也不应具有循环依赖性.这表明您的应用程序设计存在问题.

如果完全控制实例创建,我应该默认使用构造函数注入吗?

是.构造函数注入是最好的方法.它使得很容易看出一个类具有哪些依赖关系,可以实现所需的依赖关系,并防止时间耦合.

我是否正确使用构造函数注入我编写与容器无关的代码?

这是对的.构造函数注入允许您延迟决定使用哪个DI库,以及是否使用DI库.

有关上述内容的更详细说明以及更多内容,请阅读Mark Seemann(以及我自己)的" .NET中依赖注入 "一书,这是理解DI及其基本模式和原则的首选指南.

  • @hB0 这实际上是非常糟糕的建议。如果您的构造函数具有许多参数,则您的代码会告诉您违反了单一责任原则。使用属性并不能解决根本问题:你的类太大了;它应该被分成更小、更有针对性的班级。 (2认同)

B12*_*ter 7

公认的答案支持构造函数注入,并对属性注入采取相当关键的立场。因此,如果使用得当,它不会专注于解决属性注入实际解决的问题。因此,我想借此机会解决其中的一些问题,并对已接受的答案提出一些反驳。

什么时候应该使用属性注入?

想象一下,您有一个包含 100 多个控制器的项目,并且所有这些控制器都扩展了一个自定义基本控制器(父服务)。在这种情况下,服务由多个子服务扩展,使用构造函数注入是一种负担:对于您创建的每个构造函数,您需要将参数传递给父服务的构造函数。如果您决定扩展父服务的构造函数签名,您也将被迫扩展所有子服务的构造函数的签名。

为了使这个示例更加生动,假设您从一个具有无参数构造函数的基本控制器开始您的项目。

  • 现在一个月后,您决定在您的基本控制器中使用记录器服务。? 您不仅要更改基本控制器构造函数的签名,还要更改 100 多个子控制器的签名。
  • 现在又过了一个月,您需要访问基本控制器中的用户服务吗?同样,您必须更改 100 多个子控制器的构造函数签名。
  • 你明白了……

使用属性注入,您可以通过简单地将必要的属性添加到您的父服务并让您的 DI 机制通过反射处理注入来轻松规避整个不便。作为副作用,这也大大降低了合并冲突的风险(因为接触的文件减少到最低限度)。

到目前为止,我主要讨论的是控制器,但这个例子适用于任何有服务层次结构的情况——这个层次结构越深或越广,构造函数注入的负担就越大。然而,在项目中完全避免服务层次结构可能并不总是一个合理的选择。

可以说属性注入构造函数注入之间的决定实际上是实用主义和OOP“纯粹主义”之间的决定。

从“纯粹”OOP 的角度来看,规则是(如已接受的答案中所述)通过其构造函数初始化类的所有必需字段,以避免授予对处于“未完成”状态的新创建实例的任何访问权限(即可能会导致稍后抛出异常)。

参考这条规则,OOP 纯粹主义者说属性注入(暂时)使您的服务处于“未完成”状态(从构造函数返回到您的属性被注入之间的时间跨度)是有道理的,并且这增加了您的应用程序可能中断的风险。

但是,当谈论由 IoC/DI 容器管理的服务时,如果您认为 DI 机制负责在任何用户操作或 API 请求之前解决依赖关系图并连接所有内容,则这种风险实际上会降低为零使其进入您的系统或需要进行处理。例如,在调用控制器操作时,您可以确保您的服务已正确连接并注入控制器的属性(当然,前提是您事先正确配置了它们)。

此外,只有通过构造函数注入才能使您的依赖项“必需”的论点在您不负责将服务手动注入您的类而是将此任务委托给您的 IoC 机制的世界中是相当薄弱的。更糟糕的是,您可能会产生错误的安全感,因为您通过构造函数声明 aServiceX需要ServiceY- 但如果您忘记向ServiceYDI 机制注册您的 DI 机制,您只会被null注入到您ServiceX的构造函数中。

另一个反对属性注入的“论点”是,您的程序员同事更难区分由 DI 机制管理的属性和那些与 DI 无关的属性。但是,在这种情况下,您可以仅使用标记属性来“选择加入”DI或在您的属性上方添加简短注释以在情况不清楚时进行清理。此外,在服务类中,拥有引用不应该由您的 DI 机制管理的其他服务的属性是相当不寻常的。

最后,至于说构造函数注入使单元测试更容易(因为您知道类需要哪些依赖项),我只想说,使用属性注入,您很快就会注意到您在测试开始时忘记包含依赖项由于某个服务未定义而失败。

如果完全控制实例创建,我应该默认使用构造函数注入吗?

综上所述,我想我可以回答你的第二个问题:不一定。这取决于您的项目大小、您采用的服务层次结构类型、父服务的依赖项更改的频率以及您愿意在管理参数和将参数向上传递到服务层次结构方面投入多少时间和资源。

使用构造函数注入编写与容器无关的代码是否正确?

是的!– 在您没有注入容器本身的前提下……您不应该这样做!;)


综上所述,这里引用了 Martin Fowler关于依赖注入的精彩讨论,直接解决了构造函数与 setter/属性注入的问题,我可以完全订阅最后的引用:)

如果您有多个构造函数和继承,那么事情会变得特别尴尬。为了初始化所有内容,您必须提供构造函数以转发到每个超类构造函数,同时还添加您自己的参数。这可能导致构造函数的更大爆炸。

尽管有缺点,但我更喜欢从构造函数注入开始,但是一旦我上面概述的问题开​​始成为问题,请准备好切换到 setter 注入。

这个问题在提供依赖注入器作为其框架一部分的各个团队之间引起了很多争论。然而,似乎大多数构建这些框架的人已经意识到支持这两种机制很重要,即使他们偏爱其中一种。

最后一点:如果您由于某种原因,想转行从后财产注入构造器注入,没有问题,你可以随时添加与参数的构造函数被注入,并通过分配你的构造属性-死简单。

  • 一个务实的答案。感谢您提供不同的观点。 (2认同)