我知道的唯一合法的依赖注入反模式是Service Locator模式,当使用DI框架时,它是一种反模式。
我在这里或其他地方听说过的所有其他所谓的DI反模式只是一般OO /软件设计反模式的更具体案例。例如:
构造函数的过度注入违反了单一责任原则。太多的构造函数参数表示过多的依赖关系;过多的依赖关系表明该类正在尝试做太多事情。通常,此错误与其他代码气味相关,例如异常长或模棱两可的(“经理”)类名。静态分析工具可以轻松检测到过多的传入/传出耦合。
数据的注射,而不是行为,是的一个亚型骚灵反模式,在这种情况下是所述容器中的“感性。如果班级需要了解当前日期和时间,则无需插入DateTime
,即数据。取而代之的是,您在系统时钟上注入了一个抽象(我通常称呼我为ISystemClock
,尽管我认为SystemWrappers项目中有一个更通用的抽象)。这不仅对DI是正确的;这对于可测试性绝对必要,因此您可以测试时变功能,而无需实际等待它们。
在我看来,将每个生命周期都声明为Singleton是货物崇拜编程的完美示例,在较小程度上,通俗地称为“ 对象污水池 ”。我看到的单例滥用比我想记住的要多,而且很少涉及DI。
另一个常见错误是特定于实现的接口类型(具有类似的奇怪名称IOracleRepository
),只是为了能够在容器中注册它。这本身就违反了依赖反转原则(仅因为它是一个接口,并不意味着它是真正的抽象),并且通常还包括违反接口隔离原则的接口膨胀。
我通常看到的最后一个错误是“可选依赖项”,这是他们在NerdDinner中所做的。换句话说,有一个接受依赖注入的构造函数,还有另一个使用“默认”实现的构造函数。这也违反了DIP,并且也容易导致违反LSP,因为随着时间的推移,开发人员开始围绕默认实现进行假设,和/或使用默认构造函数启动实例的更新。
俗话说,您可以用任何语言编写FORTRAN。依赖注入不是防止开发人员搞砸其依赖管理的灵丹妙药,但它确实可以防止许多常见的错误/反模式:
...等等。
显然,您不想设计一个框架来依赖特定的IoC容器实现,例如Unity或AutoFac。也就是说,再次违反了DIP。但是,如果您发现自己甚至在考虑做类似的事情,那么您肯定已经犯了一些设计错误,因为依赖注入是一种通用的依赖管理技术,并且与IoC容器的概念无关。
任何东西都可以构造一个依赖树。也许是一个IoC容器,也许是带有一堆模拟的单元测试,也许是提供虚拟数据的测试驱动程序。您的框架不应该在乎,我见过的大多数框架都不在乎,但是它们仍然大量使用依赖注入,以便可以轻松地将其集成到最终用户选择的IoC容器中。
DI不是火箭科学。只是要避免new
,static
除非有迫切的理由要使用它们,例如没有外部依赖项的实用程序方法,或者在框架外可能没有任何目的的实用程序类(互操作包装和字典键是常见的例子)这个)。
当开发人员首先学习如何使用IoC框架时,会出现许多问题,而不是实际更改其处理依赖关系和抽象的方式以适合IoC模型,而是尝试操纵IoC容器以满足其期望。旧的编码风格,通常会涉及较高的耦合度和较低的内聚力。无论是否使用DI技术,错误代码都是错误代码。