介绍
迭代器模式(Iterator Pattern)是 用于顺序访问集合对象的元素,不需要知道集合对象的底层表示,是一种行为型模式。
本文就以Java中的集合为例来讲解迭代器模式的应用,由于功能类似,就只讲ArrayList。
原理
一个完整的迭代器模式一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。对于迭代器模式,我画了一张简单的类图,你可以看一看,先有个大致的印象。
为了封装迭代器的创建细节,我们可以在容器中定义一个 iterator() 方法,来创建对应的迭代器。为了能实现基于接口而非实现编程,我们还需要将这个方法定义在 List 接口中。
接下来说Iterator:Iterator需要两个方法用于判断是否有下一个元素,和获取下一个元素。
public interface Iterator<E> { //Element //Type //K //Value V Tank
boolean hasNext();
E next(); //Tank next() Iterator_<Tank> it = ... Tank t = it.next();
}
由于内部类可以直接范围主类的变量,所以把ArrayListIterator作为ArrayList 的内部类。简化后的ArrayList如下:
package com.mashibing.dp.Iterator.v7;
/**
* 相比数组,这个容器不用考虑边界问题,可以动态扩展
*/
class ArrayList_<E> implements Collection_<E> {
E[] objects = (E[])new Object[10];
//objects中下一个空的位置在哪儿,或者说,目前容器中有多少个元素
private int index = 0;
public void add(E o) {
if(index == objects.length) {
E[] newObjects = (E[])new Object[objects.length*2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index ++;
}
public int size() {
return index;
}
@Override
public Iterator_<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator<E> implements Iterator_<E> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
if(currentIndex >= index) return false;
return true;
}
@Override
public E next() {
E o = (E)objects[currentIndex];
currentIndex ++;
return o;
}
}
}
结合刚刚的例子,我们来总结一下迭代器的设计思路。总结下来就三句话:迭代器中需要定义 hasNext()、next() 两个最基本的方法。待遍历的容器对象通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器。
迭代器的优势
1、它支持以不同的方式遍历一个聚合对象。
2、迭代器简化了聚合类。
3、在同一个聚合上可以有多个遍历。
4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
遍历时注意删除和添加新元素的影响
由于在遍历时添加或删除元素会影响数组元素的位置和遍历指针指向的元素,所以有可能导致某个元素别重复遍历或者遍历不到。
有两种比较干脆利索的解决方案,来避免出现这种不可预期的运行结果。一种是遍历的时候不允许增删元素,另一种是增删元素之后让遍历报错。第一种解决方案比较难实现,因为很难确定迭代器使用结束的时间点。第二种解决方案更加合理。Java 语言就是采用的这种解决方案。增删元素之后,我们选择 fail-fast 解决方式,让遍历操作直接抛出运行时异常。
像 Java 语言,迭代器类中除了前面提到的几个最基本的方法之外,还定义了一个 remove() 方法,能够在遍历集合的同时,安全地删除集合中的元素。