即使使用DI,我们的业务/服务类型也需要在其方法中创建一些传递对象。我想说的这些传递对象总是值类型(表示纯数据)或I / O类型(表示外部状态)。值类型可以用于新建,但是我们想在测试中模拟/存根的I / O类型,因此我们无法直接创建它们。
我看到的常见解决方案是为类提供某种IOFactory依赖:在生产中,我们为生成实际I / O对象的类提供工厂;在测试中,我们为制作假I / O对象的类提供了一个工厂。
我不喜欢这样做,不仅要创建模拟/存根I / O类型,还要创建真实类型及其替代品的工厂。这感觉很麻烦,特别是在像JS这样的动态语言中,我经常可以轻松地为每个测试创建我的模拟/存根。
我遇到的另一种选择是使用类似服务定位器的注入器...
var file = injector.inject(File, '/path'); // given type, returns new instance of that type
...这样,在生产中,注入器被配置为提供真实文件,而在测试中,它被配置为返回模拟/存根。我们可以将注入器视为一个特殊的全局,但可以说注入器现在是每个需要使用它的业务类型的依赖项,因此它应该像任何其他依赖项一样注入。
我认为支持这一想法的主要论点是,在许多情况下,喷射器可以减少工厂样板(以一些额外的工厂配置工作为代价)。反对的论点是什么?工厂是否更好,因为它们更具体地声明了类需要什么,从而作为文档?或者是完全不同的正确解决方案?
答案 0 :(得分:1)
使用injector
作为Service Locator:
服务定位器有时被描述为being an anti-pattern,因为:
(一些开发人员raise objections称其为反模式,但仍普遍认为它具有特定目的且经常被滥用。)
您描述的injector
是反模式的主要示例。有了它,您的对象现在将具有隐藏的必需依赖项 - 一个未在其构造函数中声明的依赖项。
如果在对象使用注入器时未配置注入器,则会发生运行时错误。有人可能没有意识到需要这种配置才能让对象正常运行(你甚至可能是那个人,从现在起6个月)。
依赖注入背后的想法是,对象非常明确地表达它需要什么才能按预期行事。其背后的基本原理与用于界面格言的基本原理相同:Easy to use correctly; Hard to use incorrectly。
你是对的,因为引入工厂能够动态实例化对象有时会很麻烦 - 但是许多样板文件通常都是根据许多OO语言的冗长语法而存在的。不一定是依赖注入的概念。
因此,可以使用服务定位器方法以获得短期便利(就像任何其他全局变量一样) - 只要您意识到 就是这样的情况下它真正提供的所有内容
至于替代方案,不要忘记所需的依赖项不一定需要作为构造函数参数传递。
不是将工厂传递给类的构造函数,有时使用Factory Method方法是有意义的。也就是说,强制派生类来提供依赖,而不是期望它来自对象的创建者。
如果可以使用有意义的默认依赖关系(例如,Null Object)初始化SUT,则可以考虑在setter方法中注入依赖关系。
在C / C ++中,开发人员有时甚至依赖链接器来处理依赖注入。 James Grenning在他的书Test-Driven Development for Embedded C中写道:
要打破对生产代码的依赖性,请仅考虑其接口方面的协作者。 [...]接口由头文件表示,实现由源文件表示。
LightScheduler
[SUT]在链接时绑定到生产代码实现。单元测试通过提供替代实现来利用链接接缝。 (第120页)
最后,问一下你希望从依赖注入中得到什么。如果收益不超过实施它所涉及的工作,那么也许这是不必要的。但是,不要只是为了在短期内节省一点时间。