使用依赖注入时,我应该如何在运行时创建 I/O 类型(例如文件)
Posted
技术标签:
【中文标题】使用依赖注入时,我应该如何在运行时创建 I/O 类型(例如文件)【英文标题】:How should I create I/O types (e.g. File) at runtime when using Dependency Injection 【发布时间】:2014-06-01 01:43:05 【问题描述】:即使使用 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
...这样,在生产中,注入器被配置为提供一个真实的文件,而在测试中,它被配置为返回一个模拟/存根。我们可以将注入器视为一个特殊的全局,但可以说注入器现在是需要使用它的每个业务类型的依赖项,因此它应该像任何其他依赖项一样被注入。
我看到支持这个想法的主要论据是,在许多情况下,注入器可以减少工厂样板(以一些额外的工厂配置工作为代价)。反对的论据是什么?工厂是不是更好,因为它们是类需要的更具体的声明,因此可以用作文档?还是正确的解决方案完全不同?
【问题讨论】:
【参考方案1】:关于使用injector
作为Service Locator:
服务定位器有时被描述为being an anti-pattern,因为:
错误使用会导致代码难以维护 很容易误用(一些开发者raise objections 称其为反模式,但仍普遍认为它有特定目的且经常被滥用。)
您描述的injector
是反模式的主要示例。有了它,您的对象现在将有一个隐藏的 required 依赖项 - 一个未在其构造函数中声明的依赖项。
如果在对象使用它时没有配置注入器,则会发生运行时错误。有人可能没有意识到需要此配置才能使对象正常运行(您甚至可能是那个人,从现在起 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 页)
最后,问问你希望从依赖注入中得到什么。如果好处不超过实施它所涉及的工作,那么也许它是不必要的。但不要仅仅为了在短期内节省一点时间而回避它。
【讨论】:
谢谢。我想我找到了答案:让类更好地提供有关其依赖关系的尽可能具体的信息。抽象工厂并不完全具体,但它们比注入器更具体。这对于留在一个或两个人编写的单个程序中的代码可能无关紧要,但是将注入器用作服务定位器(即使只是用于 i/o 类型)对于类的任何外部使用者来说都是可怕的。跨度> 你说得对,这是一个语言问题:我真正想要的是一种方便、简洁的方式来创建样板抽象工厂。以上是关于使用依赖注入时,我应该如何在运行时创建 I/O 类型(例如文件)的主要内容,如果未能解决你的问题,请参考以下文章