没有依赖注入的对象组合是坏事吗?

时间:2009-05-03 00:46:44

标签: oop inversion-of-control

当单元测试与另一个对象具有组合关系的对象(“has-a”关系)时,据我所知,如果使用某种依赖注入,则只能模拟组合对象。因此,以下类型的代码使得单元测试非常困难,因此可能被认为是一件坏事:

<?php
class aSampleClass {
    private $dependency;
    public function __construct() {
        $this->dependency = new otherClass;
    }
}

通过将otherClass对象的实例作为参数传递给构造函数,可以轻松地将此玩具示例转换为使用依赖项注入,但情况并非总是如此。上述形式的对象组成(“new”运算符直接在类实现中使用)是一件坏事吗?您是否应该总是尝试编写一个类,以便可以独立于其关联进行全面测试?

当您使用简单的Value对象(在域驱动设计的用语中)(例如日期或货币对象)时,使用依赖注入似乎对我有所搁浅。在这些情况下,直接实例化有问题的Value对象似乎是有意义的。例如:

<?php
    class anotherSampleClass {
        public function getTimeDifferencePhrase() {
            $now = new date_Time;
            $then = new date_Time(time()-60*60*24);
            return $now->relativeTimePhrase($then);
        }
    }

当然,在这个例子中,anotherSampleClass的单元测试更有意义的是运行date_Time对象的实现,而不是尝试使用模拟对象或测试双精度。

思考?

4 个答案:

答案 0 :(得分:2)

这取决于您正在测试的 。依赖注入解决了测试类的问题,而不必担心它对外部服务的依赖(可能会失败,没有测试数据,或者不适合单元测试)。

你不会模拟一个值对象(或一个字符串,int等)只是为了测试你的类是否正确构造它并调用了差异运算符......这是实现的所有部分并且不是真正相关的测试它。

但是,当您通过getRelativeTimeDifferencePhrase时,您会测试60 * 60 * 24 seconds正确返回“24小时前”。

您将意识到您的硬编码time()导致了一个脆弱的测试 - 因为您无法准确预测代码运行时time()将返回的内容。这将导致RealTimeService,这将引入您可以控制的接缝。

让您的班级实例化RealTimeService会让您无法注入MockTimeService - 这样您就可以对其time()进行硬编码,这样您就会最终通过ITimeService到你的构造函数。现在,你有依赖注入。 ;)

答案 1 :(得分:1)

依赖注入确实解决了很多问题。然而,许多人没有看到它是一个聚合而不是一个组合。在您实例化 otherClass 的示例中,您有一个合成。因此,通过引入依赖注入,你实际上打破了Demeter的组成和定律并引入了聚合。

我认为依赖注入很好,只要它不破坏架构。很多人说在构造函数中使用new运算符很糟糕,而实例应该传递给构造函数,但它们是错误的。它应该取决于域名。有些情况甚至可能导致混乱,特别是在没有垃圾收集的语言中。例如,C ++中的经验法则是创建者销毁一个对象。因此,如果有电池的手机被摧毁,谁会破坏电池?电话或任何对象将其传递到手机?但在某些情况下,您可能希望将电池传入电话。例如,如果您正在为工厂建模。

所以,回答你的问题“没有依赖注入的对象组合是一件坏事吗?” - 这取决于你正在建模的领域,牺牲设计而不是可测试性是一件坏事IMO。如果您发现不可测试的东西,那么看看您的设计是否正确。我经常会看到我是否可以引入依赖注入。如果这是不可能的,那么我看看我是否可以引入工厂方法。如果没有,那么我会看看我是否可以做其他事情。

答案 2 :(得分:0)

在这些情景中,没有明确的规则。依赖注入通常是一件好事,但并不总是必要的。它是关于分析你的课程并弄清楚谁应该真正负责创建依赖关系。

如果您正在使用非常简单的对象,这些对象在语义上属于您的类,并且您的类对它们负全部责任,那么依赖注入可能会过度工程化。你真的不需要测试date_Time,所以我不打算注入那个特定的依赖。

要考虑的另一件事是,在构造函数中构建依赖项是否会使您的类不可测试。例如,它发生在使用随机数生成器的类中。在这种情况下,如果你不使用依赖注入,那么真的没有办法注入一个生成器,它会为你提供一组适合测试的可预测数字。

所以,简短回答:这通常是一件好事,但绝不是必要的。

答案 3 :(得分:0)

对于这样的事情,没有严格的规则。一些纯粹主义者会说你应该注入依赖,而其他人会采取更务实的方法。由于VO本身没有任何依赖关系,我可能会调用KISS原则并说它在对象中“新”它是完全没问题的。特别是VO驻留在同一个模块中(以DDD术语表示。)此外,在对象中实例化VO并不会使它变得不那么可测试。