栈的逻辑至此,通过允许传递多个行为方法,我已经实现了 push() 和 pop() 方法。但是我还没有处理栈的实际内容,这是与多个 push() 和 pop() 相关联的逻辑,间或出现一个 peek()。
首先,我将通过 shouldPopSecondPushedValueFirst() 行为确保栈的基本算法(先进先出)无误。
清单 19. 确保典型的栈逻辑1
2
3
4
5
| public void shouldPopSecondPushedValueFirst() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
}
|
清单 19 中的代码可以按计划运行,所以我将实现另一个行为方法(在清单 20 中),以确保两次使用 pop() 都能表现出正确的行为。
清单 20. 更深入地查看栈行为1
2
3
4
5
6
| public void shouldPopValuesInReverseOrder() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.pop(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 1"));
}
|
接下来,我要确保 peek() 能按预期运行。正如 Linda 所说,peek() 遵从和 pop() 相同的规则,但是 “应该保留栈顶的项目”。相应地,我在清单 21 中实现了 shouldLeaveValueOnStackAfterPeep() 方法的行为。
清单 21. 确保 peek 保留栈顶的项目1
2
3
4
5
6
| public void shouldLeaveValueOnStackAfterPeep() throws Exception{
stStack.push("test 1");
stStack.push("test 2");
Ensure.that(stStack.peek(), m.is("test 2"));
Ensure.that(stStack.pop(), m.is("test 2"));
}
|
由于 peek() 还没有定义,因此清单 21 还不能编译。在清单 22 中,我定义了 peek() 的一个最简单的实现。
清单 22. 当前,peek 是必需的1
2
3
| public E peek() {
return null;
}
|
现在 StackBehavior 类可以编译,但是它仍然不能运行。
清单 23. 返回 null 并不奇怪,对吗?1
2
3
4
5
| 1) StackBehavior should leave value on stack after peep:
VerificationException: Expected:
same instance as <test 2>
but got:
null:
|
在逻辑上,peek() 不会从内部集合中移除 项目,它只是传递指向那个项目的指针。因此,我将对 ArrayList 使用 get() 方法,而不是 remove() 方法,如清单 24 所示。
清单 24. 不要移除它1
2
3
| public E peek() {
return this.list.get(this.list.size()-1);
}
|
栈为空的情况现在重新运行 中的行为,结果顺利通过。但是,在这样做的过程中发现一个问题:如果栈为空,则 peek() 有怎样的行为?如果说栈为空时调用 pop() 会抛出一个异常,那么 peek() 是否也应该如此?
Linda 对此没有进行解释,所以,显然我需要自己添加新的行为。在清单 25 中,我为 “当之前没有调用 push() 时调用 peek() 会怎样” 这个场景编写了代码。
清单 25. 如果没有调用 push 就调用 peek,会怎样?1
2
3
| public void shouldReturnNullOnPeekWithoutPush() throws Exception{
Ensure.that(stStack.peek(), m.is(null));
}
|
同样,不会感到意外。如清单 26 所示,问题出现了。
清单 26. 没有可执行的内容1
2
| 1) StackBehavior should return null on peek without push:
java.lang.ArrayIndexOutOfBoundsException: -1
|
修复这个缺陷的逻辑类似于 pop() 的逻辑,如清单 27 所示。
清单 27. 这个 peek() 需要做一些修复1
2
3
4
5
6
7
| public E peek() {
if(this.list.size() > 0){
return this.list.get(this.list.size()-1);
}else{
return null;
}
}
|
把我对 Stack 类作出的所有修改和修复综合起来,可以得到清单 28 中的代码。
清单 28. 一个可正常工作的栈1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| import java.util.ArrayList;
public class Stack<E> {
private ArrayList<E> list;
public Stack() {
this.list = new ArrayList<E>();
}
public void push(E value) {
if(value == null){
throw new RuntimeException("Can't push null");
}else{
this.list.add(value);
}
}
public E pop() {
if(this.list.size() > 0){
return this.list.remove(this.list.size()-1);
}else{
throw new RuntimeException("Nothing to pop");
}
}
public E peek() {
if(this.list.size() > 0){
return this.list.get(this.list.size()-1);
}else{
return null;
}
}
}
|
在此,StackBehavior 类运行 7 种行为,以确保 Stack 类能按照 Linda 的(和我自己的一点)规范运行。Stack 类 还可能使用某种重构(也许 pop() 方法 应该调用 peek() 进行测试,而不是执行 size() 检查?),但是由于一直使用了行为驱动过程,我可以很自信地对代码作出更改。如果出现了问题,很快就可以收到通知。 |