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

时间:2022-09-24 23:20:55

When unit-testing objects which have a composition relationship with another object (a "has-a" relationship), as I understand it, you can only really mock the composed objects if you are using dependency injection of some sort. Hence, code of the following kind make unit-testing very difficult and might therefore be considered a bad thing:

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

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

This toy example is easily converted to using dependency injection by passing an instance of an otherClass object as a parameter to the constructor, but that's not always the case. Is object composition of the above form (where the "new" operator is used directly in a class implementation) a bad thing? Should you always try and write a class so that it can be fully tested in isolation of its associations?

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

Using dependency injection seems to run aground for me when you are using simple Value objects (in the parlance of domain-driven design) such as a date, or a money object. In those cases, it just seems to make sense to directly instantiate the Value object in question. For instance:

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

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

Surely, in this example, it makes more sense for the unit tests of anotherSampleClass to also exercise the implementation of the date_Time objects rather than trying to use mock objects or test doubles.

当然,在这个例子中,对于anotherSampleClass的单​​元测试来说,更有意义的是还可以执行date_Time对象而不是尝试使用模拟对象或测试双精度。

Thoughts?

4 个解决方案

#1


It depends on what you're testing. Dependency Injection solves the problem of testing your class, without having to worry about it's dependencies on outside services (which may fail, not have test data, or otherwise be unsuitable for a unit test).

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

You wouldn't mock a value object (or a string, int, etc.) just to test that your class correctly constructed it and called the difference operator...that's all part of the implementation and isn't really relevant to testing it.

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

You would, however, test that your getRelativeTimeDifferencePhrase correctly returns "24 hours ago" when passed 60 * 60 * 24 seconds.

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

You will then realize that your hard coding time() leads to a fragile test - as you can't predict with enough accuracy what time() will return when your code runs. This will lead to a RealTimeService, which will introduce a seam that you can control.

然后,您将意识到您的硬编码时间()会导致脆弱的测试 - 因为您无法准确预测代码运行时将返回的时间()。这将导致RealTimeService,它将引入您可以控制的接缝。

Having your class instantiate a RealTimeService will leave you no way to inject your MockTimeService - so that you can hard code what time() it is, so you then will end up passing an ITimeService to your constructor. Now, you have dependency injection. ;)

让你的类实例化一个RealTimeService会让你无法注入你的MockTimeService - 这样你就可以硬编码它的时间(),所以你最终会把ITimeService传递给你的构造函数。现在,你有依赖注入。 ;)

#2


Dependency injection does indeed solves a lot of problems. However, many people fail to see that it is an aggregation not a composition. In the example where you instantiate otherClass you have a composition. Therefore, by introducing dependency injection you are really breaking both the Composition and the Law of Demeter and introduce an aggregation.

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

I think the dependency injection is great as long as it doesn't break the architecture. Many people say that the use of new operator in a constructor is bad and instead an instance should be passed into constructor, but they are mistaken. It should depend of the domain. There are situation when it may even lead to confusion especially in languages with no garbage collection. For example, the rule of thumb in C++ is that the creator destroys an object. So if a Phone that has a Battery is destroyed, who's going to destroy the Battery? The Phone or whatever object passed it into the Phone? But in some situation you would want to pass the Battery into the Phone. For example if you're modeling a factory.

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

So, to answer your question "Is object composition without dependency injection a bad thing?" - it depends on the domain you're modeling, sacrificing design over testability is a bad thing IMO. If you find something untestable then look if your design is right. I would usually see if I can introduce dependency injection. If it's out of the question then I see if I can introduce factory methods. If not then I'll see if I can do something else.

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

#3


There isn't a definite rule one should follow in these scenarios. Dependency Injection is usually a good thing, but it's not always necessary. It's about analyzing your class and figuring out who should really have the responsibility of creating dependencies.

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

If you're working with very simple objects that, semantically, belong to your class and your class is completely responsible for them, then dependency injection might be over-engineering. You really don't need to test date_Time, so I wouldn't bother injecting that particular dependency.

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

One other thing to consider is whether having that dependency built within the constructor might make your class untestable. It happens, for example, with classes that use random number generators. In that case if you don't use dependency injection there really isn't a way to inject a generator that will give you a predictable set of numbers suitable for testing.

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

So, short answer: It's usually a good thing, but by no means always necessary.

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

#4


There is no hard-and-fast rule for something like this. Some purists would say you should inject the dependency, while others would go for a more pragmatic approach. Since the VO itself doesn't have any dependencies itself, I would probably invoke the KISS principle and say it's totally fine to "new" it up in the object. This is especially true the VO resides in the same module (in DDD terms.) Moreover, instantiating the VO in the object doesn't make it any less testable.

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

#1


It depends on what you're testing. Dependency Injection solves the problem of testing your class, without having to worry about it's dependencies on outside services (which may fail, not have test data, or otherwise be unsuitable for a unit test).

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

You wouldn't mock a value object (or a string, int, etc.) just to test that your class correctly constructed it and called the difference operator...that's all part of the implementation and isn't really relevant to testing it.

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

You would, however, test that your getRelativeTimeDifferencePhrase correctly returns "24 hours ago" when passed 60 * 60 * 24 seconds.

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

You will then realize that your hard coding time() leads to a fragile test - as you can't predict with enough accuracy what time() will return when your code runs. This will lead to a RealTimeService, which will introduce a seam that you can control.

然后,您将意识到您的硬编码时间()会导致脆弱的测试 - 因为您无法准确预测代码运行时将返回的时间()。这将导致RealTimeService,它将引入您可以控制的接缝。

Having your class instantiate a RealTimeService will leave you no way to inject your MockTimeService - so that you can hard code what time() it is, so you then will end up passing an ITimeService to your constructor. Now, you have dependency injection. ;)

让你的类实例化一个RealTimeService会让你无法注入你的MockTimeService - 这样你就可以硬编码它的时间(),所以你最终会把ITimeService传递给你的构造函数。现在,你有依赖注入。 ;)

#2


Dependency injection does indeed solves a lot of problems. However, many people fail to see that it is an aggregation not a composition. In the example where you instantiate otherClass you have a composition. Therefore, by introducing dependency injection you are really breaking both the Composition and the Law of Demeter and introduce an aggregation.

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

I think the dependency injection is great as long as it doesn't break the architecture. Many people say that the use of new operator in a constructor is bad and instead an instance should be passed into constructor, but they are mistaken. It should depend of the domain. There are situation when it may even lead to confusion especially in languages with no garbage collection. For example, the rule of thumb in C++ is that the creator destroys an object. So if a Phone that has a Battery is destroyed, who's going to destroy the Battery? The Phone or whatever object passed it into the Phone? But in some situation you would want to pass the Battery into the Phone. For example if you're modeling a factory.

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

So, to answer your question "Is object composition without dependency injection a bad thing?" - it depends on the domain you're modeling, sacrificing design over testability is a bad thing IMO. If you find something untestable then look if your design is right. I would usually see if I can introduce dependency injection. If it's out of the question then I see if I can introduce factory methods. If not then I'll see if I can do something else.

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

#3


There isn't a definite rule one should follow in these scenarios. Dependency Injection is usually a good thing, but it's not always necessary. It's about analyzing your class and figuring out who should really have the responsibility of creating dependencies.

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

If you're working with very simple objects that, semantically, belong to your class and your class is completely responsible for them, then dependency injection might be over-engineering. You really don't need to test date_Time, so I wouldn't bother injecting that particular dependency.

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

One other thing to consider is whether having that dependency built within the constructor might make your class untestable. It happens, for example, with classes that use random number generators. In that case if you don't use dependency injection there really isn't a way to inject a generator that will give you a predictable set of numbers suitable for testing.

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

So, short answer: It's usually a good thing, but by no means always necessary.

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

#4


There is no hard-and-fast rule for something like this. Some purists would say you should inject the dependency, while others would go for a more pragmatic approach. Since the VO itself doesn't have any dependencies itself, I would probably invoke the KISS principle and say it's totally fine to "new" it up in the object. This is especially true the VO resides in the same module (in DDD terms.) Moreover, instantiating the VO in the object doesn't make it any less testable.

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