我正在编写第二个使用DI的真实应用程序。总的来说,我认为它有一个更好的设计。但是有一些代码味道,我不知道如何解决。
我更喜欢使用构造函数注入,并且经常观察到我需要在构造函数中注入大约5个或更多对象。它似乎太多了,也许这是一个设计问题,没有让SRP正确。但我认为我对DI的使用也应该受到指责。
我正在寻找“最佳实践”或“经验法则”,总的来说,我似乎注入了一切,那些不在.Net框架中,是否过度了?
为了开始,以下是我注入的两个对象示例,但我不确定。
对象是真正的单例,如应用程序配置或那些小的util类,你注入它们吗? 它们似乎经常被注入,注入它们的唯一原因似乎是允许改变测试的价值,但Ayende似乎以另一种方式解决了这个问题:http://ayende.com/Blog/archive/2008/07/07/Dealing-with-time-in-tests.aspx。
几乎每个对象都使用的常见对象,例如日志记录,是否应该注入?
答案 0 :(得分:15)
我经常使用的经验法则是我注入了正确编写单元测试的方法。在执行此操作时,您有时会最终抽象出BCL类(例如DateTime.Now,File等),有时甚至是您自己的东西。注入的好东西是服务(例如ICustomerService,ICustomerUnitOfWorkFactory或ICustomerRepository)。不要注入实体,DTO和消息等内容。
然而,注入对象还有其他原因,例如能够在以后更换模块(例如,用于验证,UI或O / RM的交换机实现),以允许在团队内部或跨团队进行并行开发,以及降低维护。
我更喜欢使用构造函数注入 并经常观察到我需要 大约5个或更多的物体要注射 在构造函数中。
正如您已经注意到的那样,由于没有遵守SRP,可能会导致许多依赖关系。但是,您可以做的是将具有逻辑的公共依赖项分组到聚合服务中并将其注入到使用者中。另请参阅Mark Seemann关于Aggregate Services的文章。
像真正的单身人士一样的对象 应用程序配置或那些 小的util类,你注入了吗? 它们?
我个人并不喜欢艾恩德提出的方式。这是一个Ambient Context,它是一种特定的service locator构造。这样做会隐藏依赖关系,因为类可以调用该静态类而无需注入它。明确地注入它会使您需要单独测试时间更加清晰。除此之外,它使得很难为MSTest这样的框架编写测试,这些框架倾向于并行运行测试。没有任何对策,它会使您的测试非常不可靠。对于DateTime.Now
示例,更好的解决方案是建立IClock
接口,如建议here。正如你所看到的那样,这个答案得分远高于Ayende方法,这在同一个SO问题中显示出来。
日志等常见对象 用于几乎每个对象, 他们应该被注射吗?
我在我的代码中注入它们,因为这使得依赖关系变得清晰。但请注意,在我的代码中,我几乎不必注入记录器。仔细考虑你要记录的每一行,这不是一个真正的失败(或者应该放在其他地方的交叉问题)。当发生的事情我没想到时,我通常会抛出异常。它允许我快速找到错误。换句话说:不要过滤,但要快速失败。请问问自己:“Do I log too much?”
我希望这会有所帮助。
答案 1 :(得分:3)
我个人的经验法则是:
像服务这样的东西可以满足这两个标准 - 消费者永远不应该改变它,并且你希望能够在测试时间内替换它。使用不可变项,您仍然可能在使用对象上有一个属性,但该属性只有一个getter,而不是一个setter。如果要更改值,则必须创建对象的新实例。
是否应注入记录器?
没有理由。记录器通常通过静态类公开,并且是从配置条目中新建的,因此即使是出于测试目的,也不需要注入它们。
应该注入像应用程序配置这样的真正的单例吗?
再一次,它是一个全局可访问的对象,很容易修改以用于测试目的,因此无需注入。我唯一能注入的是消费者是否“断线”;即通过反射创建或称为Web服务或远程对象。
虽然DI是一个很好的模式,但太多好事仍然可能是不健康的。如果你感觉到代码气味越来越大,那么检查你注射的每个项目并问自己一个问题:我需要注入这个参数吗?
答案 2 :(得分:3)
一个好的起点是注入Volatile Dependencies。
您可能还希望为进一步的松散耦合注入稳定依赖关系,但如果您需要确定优先级,则可以使用Volatile Dependencies作为最佳启动。
关于构造函数过度注入,它实际上只是破坏SRP的一个症状:请参阅此相关问题:How to avoid Dependency Injection constructor madness?