何时使用属性注入?

时间:2016-10-04 09:47:29

标签: dependency-injection ioc-container

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

2 个答案:

答案 0 :(得分:12)

  

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

如果依赖项是真正可选的,当你有一个Local Default,或者当你的对象图包含循环依赖时,你应该使用属性注入。

属性注入会导致Temporal Coupling,在编写业务线应用程序时,您的依赖项永远不应该是可选的:您应该应用Null Object pattern

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

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

  

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

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

  

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

这是对的。构造函数注入允许您延迟决定使用哪个DI库,以及whether at all you use a DI library

有关上述内容的更详细说明以及更多内容,请阅读Dependency Injection in .NET(以及我自己)的书籍Mark Seemann,这是了解DI及其内容的首选指南基本模式和原则。

答案 1 :(得分:1)

已被接受的答案主张使用构造函数注入,并且对财产注入持相当批判的态度。因此,如果正确使用,它不会专注于解决财产注入实际解决的问题。因此,我想借此机会解决其中的一些问题,并为公认的答案提供一些反驳的观点。

  

何时应该使用属性注入?

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

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

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

通过属性注入,您只需在父服务中添加必要的属性,然后让DI机制通过反射来处理注入,即可轻松规避整个不便之处。副作用是,这也大大降低了合并冲突的风险(因为将要触摸的文件减少到最小)。

到目前为止,我主要讨论的是 controllers ,但是此示例考虑了您拥有服务层次结构的任何情况–该层次结构越深或越广,构造函数注入的负担就越大。但是,完全避免使用服务层次结构可能并非始终是项目中的合理选择。

可以说 property constructor注入之间的决定实际上是实用主义和OOP“纯粹主义”之间的决定。

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

参考此规则,OOP纯粹主义者有一个合理的说法,即属性注入(暂时)使您的服务处于“未完成”状态(构造函数返回之间的时间间隔)以及您注入财产的那一刻),这会增加您的应用程序可能被破坏的风险。

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

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

反对属性注入的另一个“论据”是,对于您的程序员来说,区分由DI机制管理的属性和与非DI无关的属性变得更加困难。但是,在这种情况下,当大小写不清楚时,您可以仅使用marker attribute to "opt-in" for DI或在属性上方添加简短注释来清除内容。另外,在服务类中,具有引用其他不应由您的DI机制进行管理的服务的属性是很不常见的。

最后,关于构造函数注入使单元测试更加容易(因为您知道类需要什么依赖关系),我只是争辩说,使用属性注入,您很快就会注意到由于某些服务未定义而导致测试开始失败时,您忘记了包含依赖项。

  

如果实例创建在完全受控的情况下,默认情况下应该使用构造函数注入吗?

话虽如此,我想我可以用不一定回答您的第二个问题。 这取决于项目的规模,所采用的服务层次结构的类型,父服务的依赖关系更改的频率以及您愿意花费多少时间和资源来管理参数并在服务层次结构中传递参数。

  

我对吗,使用构造函数注入,我编写了与容器无关的代码?

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


以上所述,这是马丁·福勒(Martin Fowler)出色的discussion on dependency injection引述的内容,直接解决了构造函数与setter /属性注入的问题,我可以完全赞成最后一个引号:)

  

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

     

尽管有很多缺点,我还是希望从构造函数注入开始,但是一旦我上面概述的问题开​​始成为问题,就可以立即切换到setter注入。

     

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

最后一点:如果由于某种原因,您想从属性注入切换回构造函数注入,没问题,您可以随时添加带有通过构造函数注入参数并分配属性-简而言之。

相关问题