初探设计模式之【迭代器】
一、前言
迭代器提供了一种机制来按顺序访问集合中的各元素,而不需要知道集合内部的构造。
迭代器模式最典型的应用就是 jdk 中集合类中,各种集合的迭代器实现,不论是 list, 还是 set ,都有一套一样的迭代器接口,他们的用法都一样,但是这些集合内部的实现却各不相同。
迭代器让我们不用关系集合类内部的实现,就可以实现对其元素的完整访问。
二、实现
我们可以实现一套像集合一样类似的迭代器接口。
先来构建一种场景,比如要设计一个行车记录仪,支持存储 10 天的数据,当超过 10 天了,可以自动覆盖最早的视频,然后我们需要支持使用迭代器,按时间顺序从近到远的访问。这里,我们需要自己实现迭代器功能。
先定义个 Iterator 接口, 主要定义迭代过程关键的方法。
与 JDK 中的迭代器接口十分相似:
public interface Iterator<T> {
boolean hasNext();
T next();
}
Iterable
接口
/**
* 这个类相当于标记某个类,具备迭代器的功能
*
* @param <T>
*/
public interface Iterable<T> {
Iterator<T> iterator();
}
然后再看行车记录仪的实现
DriverRecorder
类
/**
* 行车记录仪
* 最多支持 10 条数据,循环覆盖保存数据
* 内部使用一个数组保存最多10条数据
* 其迭代方式为倒序迭代
*/
public class DriverRecorder implements Iterable<RecorderItem> {
private static final int MAX_SIZE = 10;
private final RecorderItem[] items = new RecorderItem[MAX_SIZE];
private int index;
private int size;
public void add(RecorderItem item) {
items[index++] = item;
if (index == MAX_SIZE) {
index = 0;
}
//记录实际存储的数量
if (size < MAX_SIZE) {
size++;
}
}
public int size() {
return size;
}
@Override
public Iterator<RecorderItem> iterator() {
return new Itr();
}
private class Itr implements Iterator<RecorderItem> {
private int cursor = index;
private int loopCount = size;
@Override
public boolean hasNext() {
return loopCount > 0;
}
@Override
public RecorderItem next() {
loopCount--;
if (--cursor < 0) {
cursor = MAX_SIZE - 1;
}
return items[cursor];
}
}
}
再看看一下调用方:
public static void main(String[] args) {
DriverRecorder recorder = new DriverRecorder();
RecorderItem item1 = new RecorderItem("10月05日", "青年路");
RecorderItem item2 = new RecorderItem("10月06日", "北京路");
RecorderItem item3 = new RecorderItem("10月07日", "朝阳路");
RecorderItem item4 = new RecorderItem("10月08日", "香港路");
RecorderItem item5 = new RecorderItem("10月09日", "上海路");
RecorderItem item6 = new RecorderItem("10月10日", "天津路");
RecorderItem item7 = new RecorderItem("10月11日", "湖北路");
RecorderItem item8 = new RecorderItem("10月12日", "湖南路");
RecorderItem item9 = new RecorderItem("10月13日", "广东路");
RecorderItem item10 = new RecorderItem("10月14日", "广西路");
RecorderItem item11 = new RecorderItem("10月15日", "吉利路");
RecorderItem item12 = new RecorderItem("10月16日", "山西路");
recorder.add(item1);
recorder.add(item2);
recorder.add(item3);
recorder.add(item4);
recorder.add(item5);
recorder.add(item6);
recorder.add(item7);
recorder.add(item8);
recorder.add(item9);
recorder.add(item10);
recorder.add(item11);
recorder.add(item12);
Iterator<RecorderItem> iterator = recorder.iterator();
while (iterator.hasNext()) {
RecorderItem item = iterator.next();
System.out.println(item.toString());
}
}
最后看一下打印的日志:
RecorderItem{time=‘10月16日’, event=‘山西路’}
RecorderItem{time=‘10月15日’, event=‘吉利路’}
RecorderItem{time=‘10月14日’, event=‘广西路’}
RecorderItem{time=‘10月13日’, event=‘广东路’}
RecorderItem{time=‘10月12日’, event=‘湖南路’}
RecorderItem{time=‘10月11日’, event=‘湖北路’}
RecorderItem{time=‘10月10日’, event=‘天津路’}
RecorderItem{time=‘10月09日’, event=‘上海路’}
RecorderItem{time=‘10月08日’, event=‘香港路’}
RecorderItem{time=‘10月07日’, event=‘朝阳路’}
如上,我们的功能完美的实现了。外部很轻松的就能通过迭代器来访问自定义集合中的数据。
为了完成对各种集合类的遍历,我们定义了统一的迭代器接口 Iterator , 基于此我们让集合以内部类的方式实现其特有的迭代逻辑,再将自己标记为 Iterator 并返回迭代器实例,以证明自己是具备迭代能力的。具体的集合内部类结构与迭代逻辑对于客户端这个“局外人”是透明的,客户端只需要知道这个集合是可以迭代的,并向集合发起迭代请求以获取迭代器,这样就可以以大家熟知的方式遍历数据了。
三、总结
对于任何类型的集合,要防止内部机制不被暴露或破坏,以及确保用户对每个元素有足够的访问权限,迭代器模式起到了至关重要的作用。迭代器巧妙的利用了内部类的形式与集合类分离,然则“藕断丝连”,迭代器依然对内部元素保有访问权限,如此便促成了集合的完美封装,在此基础上还提供给用户一套标准的迭代器接口,使各种繁杂的遍历方式得以统一。迭代器模式的应用,能再内部事务不受干涉的前提下,保持一定的对外开放。