TDD要求您在代码中插入接缝,通过接口公开实现细节。依赖注入或协作者注入违反了信息隐藏原则。如果您的类使用协作者类,那么这些协作者的构造应该是类的内部,而不是通过构造函数或接口公开。
我还没有看到任何文献解决编写可测试代码之间的冲突,同时坚持封装,简单和信息隐藏的原则。是否以任何标准方式解决了这些问题?
答案 0 :(得分:10)
您认为实现细节的方法和类实际上是接缝,它们代表您可以改变组件并将其组合成新星座的轴。
关于封装的经典想法往往过于粗糙,因为当你隐藏很多移动部件时,你也会使代码非常不灵活。它也往往违反了许多SOLID原则 - 最突出的是Single Responsibility Principle。
大多数面向对象的设计模式往往是相当精细的,并且非常适合SOLID原则,这是适当的面向对象设计细粒度的另一个指标。
如果您的类使用协作者类,那么这些协作者的构造应该在类的内部,而不是通过构造函数或接口公开。
这是混合在一起的两种不同的东西。我同意在大多数情况下,协作者不应通过接口公开。但是,通过构造函数公开它们是正确的做法。 Constructors are essentially implementation details of a class while the interfaces provide the real API
如果要保留粗粒度API以定位默认功能,您仍可以通过提供Facade来实现此目标。有关详情,请参阅此帖子:Dependency Inject (DI) "friendly" library
答案 1 :(得分:2)
也许文学很少,因为这是一种错误的二分法?
TDD要求您在代码中插入接缝,通过接口公开实现细节。
不,注入依赖项的构造函数或方法不必是调用类使用的接口的一部分:
class Zoo {
Animal exhibit;
}
interface Animal {
void walk();
}
class Dog extends Animal {
DogFood food;
Dog(DogFood food) {
this.food = food;
}
}
如果您的类使用协作者类,那么这些协作者的构造应该在类的内部,而不是通过构造函数或接口公开。
在上面的示例中,Zoo
无法访问DogFood
,因为它已经被Dog
传送,Dog
没有露出食物。
答案 2 :(得分:2)
Mark Seemann的答案非常好。由于所有内部硬连线依赖关系,行业中的“单位”经常会破坏Single Responsibility Principle并使系统难以维护。 TDD很好地暴露了这样的缺陷。然后可以像Lego块一样构建单元,以形成实际执行有用业务逻辑的更大功能单元。真的,我认为TDD很好地支持建立良好的OO系统。
不要担心隐藏这么多东西。将private
视为“不允许您访问此”,而不是“系统当前不需要访问此”。小心使用 - 通常用于在错误的时间从外部进入物体时弄乱物体状态的东西。
答案 3 :(得分:1)
根据所讨论的语言,将TDD与OOP集成可以做一些事情。在Java中,您可以使用反射来测试私有功能,并且可以将测试放在同一个包中(最好放在单独的源代码树中),以便测试包私有功能。就个人而言,我更喜欢仅通过相关代码的公共API来测试功能。
我不知道有关该主题的任何“官方”资源,但我知道Uncle Bob已就TDD的主题撰写了大量文章,并认为它与他的OOP的“SOLID”原则兼容。 / p>
答案 4 :(得分:1)
根据我的经验,TDD非常支持面向对象设计的原则。
依赖注入不是TDD的工件,它通常用于OO框架的设计,无论开发方法如何。
依赖注入是关于松散耦合 - 如果A类使用来自B类的一个或多个对象,一个好的设计将最小化A类内部B类知识。我相信这就是你所指的那个提到'信息隐藏'。
考虑如果B类改变它的实现会发生什么。或者,一个更复杂但仍然常见的场合,如果你想根据情况动态替换B的不同子类(例如,你可能正在使用策略模式),但是做出这个决定的类不是A级。
因此,Java有接口。您可以使它依赖于B类实现的接口,而不是让A类依赖于B类。然后,您可以替换实现该接口的任何类,而无需更改A中的代码。
这包括但不限于用假物品代替测试目的。
TDD绝对使用依赖注入。就像TDD使用OO的许多原则一样。但DI是OO的原则,而不是TDD的原则。
答案 5 :(得分:0)
使用依赖项注入时,将在工厂中处理对象构造和对象图管理。对象是通过工厂创建的,因此对象创建者不会知道所创建的类的依赖关系。
例如,如果A类具有通过构造函数传递的依赖关系B和C,则B和C将由工厂方法提供。创建A的对象不会知道对象B和C.
Google Guice和Ninject等依赖注入框架可用于自动化工厂创建。