什么时候使用属性注入?

When to use property injection?

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

When should I use property injection?

您应该使用 属性 注入,以防依赖项确实是可选的,当您具有本地默认值时,或者当您的对象图包含循环依赖项时。

属性 注入然而导致 Temporal Coupling and when writing Line of Business applications, your dependencies should never be optional: you should instead apply the Null Object pattern.

您也不应使用本地默认值,即:

"A default implementation of an abstraction that's defined in the same assembly as the consumer." [DIPP&P, Section 4.2.2]

不应在业务线应用程序中使用本地默认值,因为它会使测试复杂化、隐藏依赖项并且很容易忘记配置依赖项。

对象图也不应具有循环依赖性。这是您的应用程序设计中的 indication of a problem

Should I use by default constructor injection if instance creation in fully controlled?

是的。构造函数注入是最好的方法。它使得查看 class 具有哪些依赖项变得非常容易,使得需要依赖项成为可能,并防止时间耦合。

Am I right that using a constructor injection I write container-agnostic code?

这是正确的。构造函数注入允许您延迟决定使用哪个 DI 库,并且 whether at all you use a DI library.

有关以上内容的更详细解释,以及更多内容,请阅读本书 Dependency Injection Principles, Practices, and Pattern (DIPP&P) by Mark Seemann 和我自己。

属性 注入的用处非常有限,以至于在写这本书时,Mark 和我甚至讨论过将 属性 注入标记为反模式。最后,我们觉得我们无法做出像 Ambient Context 这样强大的案例,我们确实决定在该版本中将其描述为反模式。 Ambient Context 的情况 crystal 很清楚,而 属性 注入则更加混乱。但这就是为什么我们在 属性 注入部分(4.4)中添加了许多警告注释,因为我们强烈认为 属性 注入对于大多数情况来说并不是一个好的解决方案。

尽管如此,属性 注入似乎首先解决了几个问题,例如 Constructor Over-Injection 的问题(构造函数包含许多依赖项)。然而,构造函数过度注入几乎总是由设计缺陷引起,例如:

接受的答案支持构造函数注入,并对属性注入持相当批判的态度。因此,如果使用得当,它不会将重点放在解决 属性 注入 实际解决的问题上。因此,我想借此机会解决其中的一些问题,并为已接受的答案提供一些反驳论据。

When should I use property injection?

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

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

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

使用 属性 注入 你可以很容易地避免所有的不便,只需简单地向你的父服务添加必要的属性,并让你的 DI 机制通过反射处理注入.作为副作用,这也大大降低了合并冲突的风险(因为接触的文件已减少到最低限度)。

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

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

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

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

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

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

反对 属性 注入的另一个“论点”是,您的程序员同事越来越难以区分由 DI 机制管理的属性和与 DI 无关的属性。但是,在这种情况下,如果情况不明朗,您可以只使用 或在您的属性上方添加简短注释来解决问题。此外,在服务 class 中,具有引用不应由您的 DI 机制管理的其他服务的属性是相当不寻常的。

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

Should I use by default constructor injection if instance creation in fully controlled?

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

Am I right that using a constructor injection I write container-agnostic code?

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


综上所述,这里引用了 Martin Fowler 的一些名言 discussion on dependency injection 直接解决了构造函数与 setter/property 注入的问题,我可以完全订阅最后一句话 :)

If you have multiple constructors and inheritance, then things can get particularly awkward. In order to initialize everything you have to provide constructors to forward to each superclass constructor, while also adding you own arguments. This can lead to an even bigger explosion of constructors.

Despite the disadvantages my preference is to start with constructor injection, but be ready to switch to setter injection as soon as the problems I've outlined above start to become a problem.

This issue has led to a lot of debate between the various teams who provide dependency injectors as part of their frameworks. However it seems that most people who build these frameworks have realized that it's important to support both mechanisms, even if there's a preference for one of them.

最后一句话:如果您出于某种原因想要从 属性 注入 切换回 构造函数注入 ,没问题,你总是可以添加一个带有要注入的参数的构造函数,并通过你的构造函数分配属性——非常简单。