1. 迭代器模式的概念
迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的表示。
又称为“游标模式”,是一种行为模式,当你想要访问一个聚集对象,而不管这些对象是什么都需要遍历的时候,就应该考虑迭代器模式。
它为遍历不同的聚集结构提供了如开始、下一个、是否结束、当前哪一项等统一的接口。
遍历是我们日常开发中最常用到的一种方式,但有时候让我们的遍历一些奇奇怪怪的集合时,我们会无从下手。
比如我在刚学习HashMap
时,就对其遍历方式感到苦手,因为它的 map.get(0)
并不是去访问map中的第0个元素,而是访问Map中Hash值为0的元素。那我就纳闷了,我要怎样才能顺序遍历这个Map的所有元素呢?同样的 Set
也是。
上网搜索后,发现它的有一种遍历方式,那就是使用迭代器来遍历 :
<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
就这样,就遍历完Map集合了。这样的代码我们在以前学习SQLite的时候,对表进行遍历的时候用到过,没错,在数据更加庞杂的数据库中,遍历的时候更需要借助迭代器来进行遍历。
而在我们的日常开发中,基本使用不到这个模式,是因为 Java、C++、Python都已经对大部分常用的容器封装好了迭代器模式。我们直接使用就是了,所以学习迭代器模式,并不是在项目中使用它,而是知道它怎么用就是了。
2. UML图
来看下 迭代器模式的UML图:
它有四个角色:
-
Iterator
迭代器接口,负责定义、访问和遍历元素的接口。 -
ConcreteIterator
具体迭代器类的目的主要是实现迭代器接口,并记录遍历的当前位置 -
Aggreate
容器接口,容器接口负责提供创建具体迭代器角色的接口 - ConcreteAggreagte
具体容器类,具体迭代器角色与该容器相关联。
来看看代码的实现,首先是实现迭代器抽象类 Iterator,这里用了泛型的写法:
// 用于定义开始对象、得到下一个对象、判断是否到结尾、当前对象等抽象方法
public abstract class Iterator<T> {
public abstract T first();
public abstract T next();
public abstract boolean isDone();
public abstract T currentItem();
}
接下来是 聚集体抽象类:
// 创建迭代器
public abstract class Aggreate<E> {
public abstract Iterator<E> createIterator();
public abstract int count();
}
ConcreteIterator
为具体迭代器类,继承自Iterator
public class ConcreteIterator<T> extends Iterator<T> {
// 定义了一个聚集对象
private ConcreteAggregate<T> aggregate;
private int current = 0;
// 初始化时具体的聚集对象传入
public ConcreteIterator(ConcreteAggregate<T> aggregate) {
this.aggregate = aggregate;
}
// 得到聚集的第一个对象
@Override
public T first() {
return aggregate.get(0);
}
// 得到聚集的下一个对象
@Override
public T next() {
T ret = null;
current++;
if (current < aggregate.count()) {
ret = aggregate.get(current);
}
return ret;
}
// 判断当前是否遍历到结尾,到结尾返回false
@Override
public boolean isDone() {
return current >= aggregate.count();
}
// 返回当前的聚集对象
@Override
public T currentItem() {
return aggregate.get(current);
}
}
最后是具体聚集类,这里封装了一个ArrayList:
public class ConcreteAggregate<E> extends Aggreate<E> {
// 用于存放具体的数据变量
public List<E> data = new ArrayList<>();
@Override
public Iterator<E> createIterator() {
return new ConcreteIterator<>(this);
}
public void add(E e) {
data.add(e);
}
public E get(int i) {
return data.get(i);
}
@Override
public int count() {
return data.size();
}
}
最后是客户端代码:
public static void main(String[] args){
ConcreteAggregate<Integer> a = new ConcreteAggregate<>();
a.add(1);
a.add(15);
a.add(7);
a.add(12);
a.add(0);
Iterator<Integer> iterator = new ConcreteIterator<>(a);
//开始迭代
while (!iterator.isDone()){
System.out.println(iterator.currentItem());
//下一个
iterator.next();
}
}
可以看到,相比于直接去访问和遍历ConcreteAggregate
,使用迭代器模式西的我们已经不用关心 a 本身的结构。
3. Android中的使用
在开篇说过, 我们在使用SQLite的cursor游标,体现的就是迭代器模式。
在 ContentProvider的使用中,我们会使用类似的代码:
= Uri.parse("content://xxx");
Cursor cursor = getContentResolver().query(uri,null,null,null,null);
cursor.moveToFirst();
do{
String name2 = cursor.getString(cursor.getColumnIndex("name"));
Log.e(TAG, "onClick: "+name2 );
}while(cursor.moveToNext());
cursor.close();
其中的 cursor.moveToFirst()
、 cursor.moveToNext()
都是迭代器的用法。
4. 小结
对于迭代器模式来说,其自身的有点很明显也很单一,支持以不同的方式去遍历一个容器对象,也可以有多个遍历,弱化了容器与遍历算法之间的关系。
其缺点就是对类文件的增加而已。
迭代器模式发展至今,几乎每一种高级语言都有相应的内置实现,对于开发者而言,已经极少会去由自己来实现迭代器了,所以其学习价值远大于使用价值。