使用依赖项注入时,在一类中可以接受多少次注入


9

我正在C#中使用Unity进行依赖项注入,但是该问题应该适用于使用依赖项注入的任何语言和框架。

我试图遵循SOLID原理,因此得到了很多抽象。但是现在我想知道是否有一个最佳实践,一个班级应该注入多少次注入?

例如,我有一个包含9个注入的存储库。这对于其他开发人员来说很难理解吗?

注射具有以下职责:

  • IDbContextFactory-为数据库创建上下文。
  • IMapper-从实体到领域模型的映射。
  • IClock-摘要DateTime.Now以帮助进行单元测试。
  • IPerformanceFactory-测量特定方法的执行时间。
  • ILog-用于记录的Log4net。
  • ICollectionWrapperFactory-创建集合(扩展IEnumerable)。
  • IQueryFilterFactory-基于将查询数据库的输入生成查询。
  • IIdentityHelper-检索登录的用户。
  • IFaultFactory-创建不同的FaultException(我使用WCF)。

我对如何委派职责并不感到失望,但是我开始对可读性感到担心。

所以,我的问题是:

一个班应该进行多少次注射是否有限制?如果是这样,如何避免呢?

多次注入会限制可读性,还是实际上会提高可读性?


2
用数字来衡量质量通常和您每月编写的代码行所付出的代价一样糟糕。但是,您包括了依赖关系的实际示例,这是一个绝妙的主意。如果我是您,我将重新提出您的问题,以消除计数和严格限制的概念,并着重于质量本身。重新制定它时,请注意不要让您的问题过于具体。
Arseni Mourzenko

3
可接受多少个构造函数参数?IoC 不会改变这一点
Telastyn

人们为什么总是想要绝对极限?
Marjan Venema

1
@Telastyn:不一定。IoC所做的更改是,所有依赖项不再依赖于静态类/子集/全局变量,而是更加集中且更加“可见”。
Arseni Mourzenko '16

@MarjanVenema:因为它使生活更加轻松。如果您确切地知道每个方法的最大LOC或一个类中的最大方法数或一个方法上的最大变量数,而这是唯一要紧的事情,那么很容易将代码的优劣分为:以及“修复”错误代码。不幸的是,现实生活比这复杂得多,并且许多指标几乎无关紧要。
Arseni Mourzenko '16

Answers:


11

过多的依赖关系可能表明类本身做得太多。为了确定它是否做得太多:

  • 看课本身。将其分为两个,三个,四个是否有意义?整体上有意义吗?

  • 查看依赖项的类型。哪些是特定于域的,哪些是“全局”的?例如,我不会考虑ILog与以下级别相同IQueryFilterFactory:如果大多数业务类正在使用日志记录,则无论如何它们都可以使用第一个。另一方面,如果您发现很多特定于域的依赖项,则可能表明该类做得太多。

  • 查看可以用值替换的依赖项。

    IClock-摘要DateTime.Now以帮助进行单元测试。

    可以DateTime.Now通过直接传递给需要知道当前时间的方法来轻松地替换它。

通过查看实际的依赖关系,我看不到任何表明不良情况发生的迹象:

  • IDbContextFactory-为数据库创建上下文。

    好的,我们可能在业务层中,在该层中,类与数据访问层进行交互。看起来不错

  • IMapper-从实体到领域模型的映射。

    没有整体情况就很难说什么。可能是架构错误,并且映射应该直接由数据访问层完成,或者可能是架构非常好。在所有情况下,这里都具有这种依赖性是有意义的。

    另一种选择是将类分为两类:一种是处理映射,另一种是处理实际的业务逻辑。这将创建一个事实上的层,它将BL与DAL进一步分开。如果映射很复杂,那可能是个好主意。但是,在大多数情况下,这只会增加无用的复杂性。

  • IClock-摘要DateTime.Now以帮助进行单元测试。

    拥有单独的接口(和类)来获取当前时间可能不是很有用。我只是将传递DateTime.Now给需要当前时间的方法。

    如果还有其他信息(例如时区或日期范围等),则单独的类可能有意义。

  • IPerformanceFactory-测量特定方法的执行时间。

    参见下一点。

  • ILog-用于记录的Log4net。

    这种超越功能应该属于该框架,并且实际的库应在运行时可互换和可配置(例如,通过.NET中的app.config)。

    不幸的是,情况还不是这样,您可以选择一个库并坚持使用它,或者创建一个抽象层,以便以后需要时可以交换库。如果您的意图是独立于库的选择,那就去吧。如果您确定可以继续使用库多年,请不要添加抽象。

    如果该库太复杂而无法使用,则可以使用外观模式。

  • ICollectionWrapperFactory-创建集合(扩展IEnumerable)。

    我认为这会创建域逻辑所使用的非常具体的数据结构。它看起来像一个实用程序类。而是,对每个数据结构使用一个带有相关构造函数的类。如果初始化逻辑在适合构造函数的过程中有点复杂,请使用静态工厂方法。如果逻辑更加复杂,请使用工厂或构建器模式。

  • IQueryFilterFactory-基于将查询数据库的输入生成查询。

    为什么不在数据访问层中呢?为什么Filter名字有一个?

  • IIdentityHelper-检索登录的用户。

    我不确定为什么会有Helper后缀。在所有情况下,其他后缀也不会特别明确(IIdentityManager?)

    无论如何,在这里具有这种依赖性是很有意义的。

  • IFaultFactory-创建不同的FaultException(我使用WCF)。

    逻辑如此复杂以至于需要使用工厂模式?为什么要使用依赖注入?您是否会在生产代码和测试之间交换异常的创建?为什么?

    我会尝试将其重构为简单的throw new FaultException(...)。如果在将某些全局信息传播到客户端之前应将其添加到所有异常中,则WCF可能具有一种机制,您可以捕获未处理的异常并将其更改并重新抛出给客户端。

一个班应该进行多少次注射是否有限制?如果是这样,如何避免呢?

用数字来衡量质量通常和您每月编写的代码行所付出的代价一样糟糕。在设计良好的类中,您可能会有大量的依赖项,因为您可以使用很少的依赖项来创建a脚的类。

多次注入会限制可读性,还是实际上会提高可读性?

大量的依赖关系使逻辑更加难以遵循。如果逻辑很难遵循,则该类可能做得太多,应该拆分。


感谢您的评论和宝贵的时间。主要是关于FaultFactory,它将被移至WCF-logic。我会保留IClock,因为它在TDD应用程序时可以节省生命。您有时要确保在给定的时间设置了特定值。那么DateTime.Now因为它不是可模拟的,所以并不总是足够的。
smoksnes

7

这是DI的经典示例,告诉您您的班级可能变得太大而无法成为一个班级。这通常被解释为“哇,DI使我的构造函数比木星大,这种技术太可怕了”,但实际上告诉您的是“您的类有很多依赖项”。知道这一点,我们可以

  • 通过开始建立新的依赖关系来彻底解决问题
  • 重新考虑我们的设计。也许某些依赖项总是在一起,应该隐藏在其他一些抽象的后面。也许您的类应该分成2类。也许它应该由几个类组成,每个类都需要一小部分依赖项。

管理依赖性的方法有无数种,如果不了解代码和应用程​​序,就不可能说出哪种方法最适合您。

要回答您的最终问题:

  • 一个类应该有多少个依赖项有上限吗?

是的,上限是“太多”。“太多”是多少?“太多”是指班级的凝聚力变得“太低”。一切取决于。通常,如果您对一个类的反应是“哇,这个东西有很多依赖关系”,那就太多了。

  • 注入依赖关系会改善或损害可读性吗?

我认为这个问题具有误导性。答案可以是是或否。但这不是最有趣的部分。注入依赖项的目的是使它们可见。这是关于制作一个不会说谎的api。这是关于防止全球状态。这是关于使代码可测试。这是关于减少耦合。

具有合理设计和命名方法的设计良好的类比不良设计的类更具可读性。DI本身并没有真正改善或损害可读性,它只是使您的设计选择脱颖而出,如果选择不好,则会刺痛您的视线。这并不意味着DI会使您的代码可读性降低,它只是向您表明您的代码已经一团糟,您只是将其隐藏了。


1
良好的反馈。谢谢。是的,上限是“太多”。-太棒了,如此。
smoksnes

3

根据无处不在的“代码完成”一书的作者史蒂夫·麦康奈尔(Steve McConnel)的说法,经验法则是,超过7种代码味道会损害可维护性。我个人认为,在大多数情况下,该数量会减少,但是当您非常接近“合成根”时,正确执行DI 将导致要注入的依赖项很多。这是正常现象,是预期的。这是IoC容器有用且值得将它们添加到项目中的复杂性的原因之一。

因此,如果您非常接近程序的入口点,这是正常且可以接受的。如果您更深入地了解程序的逻辑,则应该解决这种气味。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.