相信大家对迭代器模式还是比较熟悉的,在Java的集合中有比较多的应用。比如你想使用迭代器遍历一个集合,代码可能是这样:
for (Iterator it = collection.iterator(); it.hasNext();)
{
doSomething(it.next());
}
迭代器的作用在于对数据的遍历与数据的内部表示进行了分离。通过迭代器,你知道调用hasNext()来确认是否还有下一个元素。如果有,那么就可以调用next()取得下一个元素。至于数据内部如何表示,我们并不关心。我们关心的只是能以一种预先定义的顺序来访问每个元素。
组合模式用来表示一个节点内部包含若干个其它的节点,而被包含的节点同样也可以再包含另外的节点。因此是一种递归的结构。不包含其他节点的节点叫做叶子节点,这通常是递归的终结点。树形结构比较适合用来说明组合模式。
假设我们用Node接口表示一个节点。可以往这个节点上添加节点,也可以获得这个节点的迭代器,用来遍历节点中的其他节点。
public interface Node {
void addNode(Node node);
Iterator<Node> iterator();
}
抽象类AbstractNode实现这个接口,并不做多余的事情。只是让这个节点有个名字,以区分于其他节点。
public abstract class AbstractNode implements Node {
protected String name;
protected AbstractNode(String name)
{
this.name = name;
}
public String toString()
{
return name;
}
}
接下来,是表示两种不同类型的节点,树枝节点和叶子节点。它们都继承自AbstractNode,不同的是叶子节点没有子节点。
public class LeafNode extends AbstractNode {
public LeafNode(String name) {
super(name);
}
public void addNode(Node node) {
throw new UnsupportedOperationException("Can’t add a node to leaf.");
}
public Iterator<Node> iterator() {
return new NullIterator<Node>();
}
}
可以看到,试图往叶子节点添加节点会导致异常抛出。而获取叶子节点的迭代器,则返回……NullIterator。这个是什么?因为叶子节点没有子节点,所以叶子节点的迭代器没有什么意义。我们可以简单地返回null,但是那样处理节点的代码就需要区分是叶子节点还是其他节点。如果我们返回的是一个NullIterator,一个看起来和其他的迭代器差不多,但是hasNext()永远返回false,而next()永远返回null的迭代器,那么叶子节点的遍历也就和其他节点没有什么两样了。
接下来是树枝节点:
public class BranchNode extends AbstractNode {
public BranchNode(String name) {
super(name);
}
private Collection<Node> childs = new ArrayList<Node>();
public void addNode(Node node) {
childs.add(node);
}
public Iterator<Node> iterator() {
return childs.iterator();
}
}
树枝节点可以添加子节点,叶子节点或者另外一个树枝节点。但是我们注意到,这里的迭代器只是简单地返回了该节点直属子节点集合的迭代器。什么意思呢?这意味着如果你通过这个迭代器去遍历这个节点,你能获得是只是所有直接添加到这个节点上的子节点。要想递归地遍历子节点的子节点,以及子节点的子节点的子节点……我们就需要一个特殊的迭代器。
用一个深度优先遍历的迭代器怎么样?所谓深度优先,通俗的讲就是沿着一条路走下去,直到走不通为止,再回过头来看看有没有别的路可走。
public class DepthFirstIterator implements Iterator<Node> {
private Stack<Iterator<Node>> stack = new Stack<Iterator<Node>>();
public DepthFirstIterator(Iterator<Node> it) {
stack.push(it);
}
public boolean hasNext() {
if (stack.isEmpty()) {
return false;
} else {
Iterator<Node> it = stack.peek();
if (it.hasNext()) {
return true;
} else {
stack.pop();
return hasNext();
}
}
}
public Node next() {
if (hasNext()) {
Iterator<Node> it = stack.peek();
Node next = it.next();
if (next instanceof BranchNode) {
stack.push(next.iterator());
}
return next;
} else {
return null;
}
}
public void remove() {
throw new UnsupportedOperationException("Can’t remove node, yet");
}
}
代码不是很长,理解起来可比较费劲。这不仅是因为我很懒没有写注释,更是因为有可恶的递归在。先从构造函数看起,一个包含了迭代器的构造函数,也就是说,这是个迭代器的迭代器……这该怎么理解呢?可以把它理解成树枝节点迭代器的一个改良的迭代器。树枝节点的迭代器只知道如何找到第一级子节点,而这个迭代器则可以沿着子节点一直寻找下去,直到找到叶子节点,然后再返回来继续寻找。好吧,说起来简单,怎么做呢?
先看如何找到“下一个”节点吧,看next()方法:
如果存在下一个节点,那么开始下一个节点的寻找之旅。否则返回null结束。
首先,通过树枝节点自带的迭代器,找到树枝节点的第一个子节点,这个子节点就是我们要找的“第一个”节点。这很简单,对吧?那么下一个节点是哪一个?这取决于我们找到的第一个节点是什么类型,如果是叶子节点,那么很简单,下一个节点跟树枝节点迭代器定义的下一个节点一样,也就是树枝节点的第二个直属子节点。如果是树枝节点呢?这个时候它将被当成一个新的起点,被压入堆栈,下一次遍历将从这个节点开始重复上面的逻辑,也就是递归。听起来并不复杂,不是吗?
责任编辑:小草