我正在学习测试驱动的开发,我注意到它会强制松散耦合的对象,这基本上是一件好事.然而,这有时也会迫使我为通常不需要的属性提供访问器,我认为SO上的大多数人都认为访问器通常是设计糟糕的标志.做TDD时这是不可避免的吗?
这是一个例子,没有TDD的实体的简化绘图代码:
class Entity {
private int x;
private int y;
private int width;
private int height;
void draw(Graphics g) {
g.drawRect(x, y, width, height);
}
}
Run Code Online (Sandbox Code Playgroud)
实体知道如何绘制自己,这很好.一切都在一个地方.但是,我正在做TDD,所以我想通过我即将实现的"fall()"方法检查我的实体是否正确移动了.以下是测试用例的样子:
@Test
public void entityFalls() {
Entity e = new Entity();
int previousY = e.getY();
e.fall();
assertTrue(previousY < e.getY());
}
Run Code Online (Sandbox Code Playgroud)
我必须查看对象的内部(好的,至少逻辑上)状态,并查看位置是否正确更新.因为它实际上是在路上(我不希望我的测试用例依赖于我的图形库),所以我将绘图代码移动到"Renderer"类:
class Renderer {
void drawEntity(Graphics g, Entity e) {
g.drawRect(e.getX(), e.getY(), e.getWidth(), e.getHeight());
}
}
Run Code Online (Sandbox Code Playgroud)
松散耦合,很好.我甚至可以用一个以完全不同的方式显示实体的渲染器替换渲染器.但是,我不得不公开实体的内部状态,即所有属性的访问器,以便渲染器可以读取它.
我觉得这是由TDD特别强制的.我该怎么办?我的设计可以接受吗?Java是否需要C++中的"friend"关键字?
更新:
感谢您迄今为止的宝贵意见!但是,我担心我选择了一个不好的例子来说明我的问题.这完全弥补了,我现在将演示一个更接近我的实际代码:
@Test
public void entityFalls() {
game.tick();
Entity initialEntity = mockRenderer.currentEntity;
int numTicks = mockRenderer.gameArea.height
- mockRenderer.currentEntity.getHeight();
for (int i = 0; i < numTicks; i++)
game.tick();
assertSame(initialEntity, mockRenderer.currentEntity);
game.tick();
assertNotSame(initialEntity, mockRenderer.currentEntity);
assertEquals(initialEntity.getY() + initialEntity.getHeight(),
mockRenderer.gameArea.height);
}
Run Code Online (Sandbox Code Playgroud)
这是一种基于游戏循环的游戏实现,其中实体可以倒下等等.如果它到达地面,则会创建一个新实体.
"mockRenderer"是接口"Renderer"的模拟实现.这个设计部分是由TDD强制的,但也是因为我要在GWT中编写用户界面,并且浏览器中还没有明确的绘图(所以),所以我认为不可能Entity类承担责任.此外,我希望将来可以将游戏移植到原生Java/Swing.
更新2:
再考虑一下这个问题,也许可以.也许可以将实体和绘图分开,并且实体告诉其他对象足够自己绘制.我的意思是,我怎么能实现这种分离呢?如果没有它,我真的不知道如何生活.甚至伟大的面向对象程序员有时也会使用带有getter/setter的对象,特别是像实体对象这样的东西.也许getter/setter并非都是邪恶的.你怎么看?
务实的程序员讨论告诉,不要问.您不想知道实体,您希望它被绘制.告诉它在给定的Graphics上绘制自己.
您可以重构上面的代码,以便实体执行绘图,如果实体不是矩形但实际上是圆形,这很有用.
void Entity::draw(Graphics g) {
g.drawRect(x,y, width, height);
}
Run Code Online (Sandbox Code Playgroud)
然后,您将检查g是否在测试中调用了正确的方法.
你说你觉得你想出的 Renderer 类是 TDD“特别强迫”的。那么,让我们看看 TDD 将您带向何方。从负责坐标和绘制自身的 Rectangle 类,到具有维护其坐标的单一职责的 Rectangle 类,以及具有渲染矩形的单一职责的渲染器。这就是我们所说的“测试驱动”的意思 - 这种做法会影响您的设计。在这种情况下,它促使您进行更严格遵守单一职责原则的设计- 如果没有测试,您就不会采用这种设计。我认为这是一件好事。我认为您很好地练习了 TDD,而且我认为这对您很有用。