编写框架时的依赖注入/ IoC容器实践


18

我在许多项目中都为.Net使用了各种IoC容器(Castle.Windsor,Autofac,MEF等)。我发现它们往往经常被滥用,并鼓励许多不良行为。

是否有用于IoC容器的既定实践,特别是在提供平台/框架时?作为框架编写者,我的目标是使代码尽可能简单和易于使用。我宁愿写一行代码来构造一个对象,而不是十行甚至两行。

例如,我注意到了一些代码气味,并且对以下内容没有好的建议:

  1. 构造函数的大量参数(> 5)。创建服务往往很复杂;尽管所有组件很少是可选的(除了可能在测试中),所有依赖项都是通过构造函数注入的。

  2. 缺乏私人和内部阶级;这可能是使用C#和Silverlight的特定限制,但是我对如何解决它感兴趣。如果所有的类都是公共的,很难说框架接口是什么。它使我可以访问我可能不应该接触的私人部件。

  3. 将对象生命周期耦合到IoC容器。手动构造创建对象所需的依赖项通常很困难。对象生命周期通常由IoC框架管理。我见过大多数班级都注册为Singletons的项目。您缺乏明确的控制,还被迫管理内部(与上述要点有关,所有类都是公共的,因此您必须注入它们)。

例如,.Net框架具有许多静态方法。例如DateTime.UtcNow。很多次,我都将这种包装和注入作为构造参数。

依赖于具体的实现使我的代码难以测试。注入依赖关系会使我的代码难以使用-特别是在类具有许多参数的情况下。

如何提供可测试的界面以及易于使用的界面?最佳做法是什么?


1
他们鼓励哪些不良做法?至于私有类,如果您具有良好的封装,则不需要太多。一方面,它们很难测试。
亚伦诺特

错误1和2是错误的做法。大型构造函数,是否不使用封装来建立最小接口?另外,我说了私有和内部的(使用内部可见的对象进行测试)。我从未使用过强迫我使用特定IoC容器的框架。
戴夫·希利尔

2
是否有可能需要为类构造函数使用许多参数的标志本身就是代码的味道,而DIP只是使这一点更加明显?
dreza 2012年

3
在框架中使用特定的IoC容器通常不是一个好习惯。使用依赖注入 一个好习惯。您知道区别吗?
Aaronaught 2012年

1
@Aaronaught(从死里复活)。使用“依赖注入是一种好习惯”是错误的。依赖注入是仅在适当时才应使用的工具。一直使用依赖注入是一个坏习惯。
戴夫·希利尔

Answers:


27

我知道的唯一合法的依赖注入反模式是Service Locator模式,当使用DI框架时,它是一种反模式。

我在这里或其他地方听说过的所有其他所谓的DI反模式只是一般OO /软件设计反模式的更具体案例。例如:

  • 构造函数的过度注入违反了单一责任原则。太多的构造函数参数表示过多的依赖关系;过多的依赖关系表明该类正在尝试做太多事情。通常,此错误与其他代码气味相关,例如异常长或模棱两可的(“经理”)类名。静态分析工具可以轻松检测到过多的传入/传出耦合。

  • 数据的注射,而不是行为,是的一个亚型骚灵反模式,在这种情况下是所述容器中的“感性。如果班级需要了解当前日期和时间,则无需插入DateTime,即数据。取而代之的是,您在系统时钟上注入了一个抽象(我通常称呼我为ISystemClock,尽管我认为SystemWrappers项目中有一个更通用的抽象)。这不仅对DI是正确的;这对于可测试性绝对必要,因此您可以测试时变功能,而无需实际等待它们。

  • 在我看来,将每个生命周期都声明为Singleton是货物崇拜编程的完美示例,在较小程度上,通俗地称为“ 对象污水池 ”。我看到的单例滥用比我想记住的要多,而且很少涉及DI。

  • 另一个常见错误是特定于实现的接口类型(具有类似的奇怪名称IOracleRepository),只是为了能够在容器中注册它。这本身就违反了依赖反转原则(仅因为它是一个接口,并不意味着它是真正的抽象),并且通常还包括违反接口隔离原则的接口膨胀

  • 我通常看到的最后一个错误是“可选依赖项”,这是他们在NerdDinner中所做的。换句话说,有一个接受依赖注入构造函数,还有另一个使用“默认”实现的构造函数。这违反了DIP,并且也容易导致违反LSP,因为随着时间的推移,开发人员开始围绕默认实现进行假设,和/或使用默认构造函数启动实例的更新。

俗话说,您可以用任何语言编写FORTRAN。依赖注入不是防止开发人员搞砸其依赖管理的灵丹妙药,但它确实可以防止许多常见的错误/反模式:

...等等。

显然,您不想设计一个框架来依赖特定的IoC容器实现,例如Unity或AutoFac。也就是说,再次违反了DIP。但是,如果您发现自己甚至在考虑做类似的事情,那么您肯定已经犯了一些设计错误,因为依赖注入是一种通用的依赖管理技术,并且与IoC容器的概念无关

任何东西都可以构造一个依赖树。也许是一个IoC容器,也许是带有一堆模拟的单元测试,也许是提供虚拟数据的测试驱动程序。您的框架不应该在乎,我见过的大多数框架都不在乎,但是它们仍然大量使用依赖注入,以便可以轻松地将其集成到最终用户选择的IoC容器中。

DI不是火箭科学。只是要避免newstatic除非有迫切的理由要使用它们,例如没有外部依赖项的实用程序方法,或者在框架外可能没有任何目的的实用程序类(互操作包装和字典键是常见的例子)这个)。

当开发人员首先学习如何使用IoC框架时,会出现许多问题,而不是实际更改其处理依赖关系和抽象的方式以适合IoC模型,而是尝试操纵IoC容器以满足其期望。旧的编码风格,通常会涉及较高的耦合度和较低的内聚力。无论是否使用DI技术,错误代码都是错误代码。


我有几个问题。对于在NuGet上分发的库,我不能依赖IoC系统,因为这是由使用库的应用程序完成的,而不是库本身。在许多情况下,库的用户确实根本不在乎其内部依赖性。使用默认构造函数初始化内部依赖关系,以及使用更长的构造函数进行单元测试和/或自定义实现是否存在问题?你也说避免“新”。这是否适用于StringBuilder,DateTime和TimeSpan之类的东西?
艾蒂安·查兰
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.