定义
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
提供一种方法访问集合中的元素,而不需要暴露集合底层的数据结构。
实列
出国旅游时,为了避免把时间浪费在熟悉该国各个景点的地理位置以及规划景点的游玩次序,这时我们可以选择熟悉该国景点的导游,按他规划好的次序游玩该国的各景点,这样我们就可以不用熟悉该国各个景点的地理位置。
国家与景点之间是集合与元素的关系,导游是国家提供的服务,而我们只和导游交互而不直接与国家交互,这种关系和迭代器模式各个角色之间的关系非常相似。
迭代器模式在程序中最典型的应用是在Java集合框架中,其中每个具体的集合类如LinkedList、ArrayList都会暴露一个iterator操作,客户端使用迭代器遍历集合中的元素就可以不用关心该集合底层的数据结构。
故事
前不久,我入职了一家开发高考刷题软件的教育公司,刚入职就领到了一个需求:他们希望我将试卷中的题目按默认顺序依次转化到PDF文件中,供用户下载。
原来的程序中有一个试卷类Paper,它里面有一个getQuestion操作可以根据次序获取试卷中的题目。
我想这很简单,新建一个Client类使用for循环取出试卷中的题然后存储到PDF中便可以了。
可是,后来他们又希望我再写一个单独的模块将试卷中的题目按随机的顺序转化到word文件中,而且还说遍历顺序不是固定,以后可能要按难度遍历试卷。
伪代码如下:
/**模拟遍历题目 */
public class Client {
public static void main(String[] args) {
IPaper paper = new Paper();
for (int i = 0; i < paper.getSize(); i++) {
Question question = paper.getQuestion(i);
appendToPDF(question);
}
}
}
问题
故事中,试卷和题目是集合与元素之间的关系,主要的需求是支持不同场景下按不同的次序遍历集合中的元素。
但是,如果像上面一样由客户端使用for循环来遍历,那么多个客户端的遍历算法就会重复,而且客户端还需要了解集合底层的实现结构,如果是简单的列表结构倒是可以依次获取,但如果是树结构或者图结构那么客户端的遍历算法就会变得很复杂。
这会造成客户端与集合底层的实现结构耦合,而且不便于扩展。所以,有没有一种方式即可以将客户端与具体的遍历行为分离达到解藕、复用的目的,又能支持多种遍历次序的扩展?这便是迭代器模式。
方案
迭代器模式通过一个被称之为迭代器的对象,将如何遍历集合元素的逻辑封装到这个对象中,由它向客户端提供获取下一个元素的操作,这样就对客户端隐藏了集合的底层实现结构。
而且,这个对象又实现自一个迭代器接口,这样在新增遍历方式时,就可以实现该接口而不需要修改客户端的代码就可以扩展新的遍历方式。
实现
接下来,我们使用迭代器模式实现一下故事中的程序。
首先,为试卷声明一个迭代器接口,它有两个操作hasNext、next。
/**试卷迭代器*/
public interface Iterator {
public boolean hasNext();
public Question next();
}
然后,创建两个实现迭代器接口的具体迭代器:默认迭代器、随机迭代器,它们封装了遍历题目的逻辑。
/**具体迭代器*/
public class DefaultIterator implements Iterator{
protected Paper paper;
protected int index=0;
public ConcreteIterator(Paper paper){
this.paper = paper;
}
@Override
public boolean hasNext() {
if(index<paper.getSize()){
return true;
}else{
return false;
}
}
@Override
public Question next() {
if(index<paper.getSize()){
Question question = paper.getQuestion(index);
index++;
return question;
}
return null;
}
}
/**随机迭代器*/
public class RandomIterator implements Iterator{......}
现在,我们还要为试卷接口添加两个创建具体迭代器的操作。如果你希望期望创建迭代器更具有动态性,那么可以考虑使用工厂模式封装创建行为。
/**试卷接口*/
public interface IPaper {
/**创建默认迭代器*/
public Iterator createDefaultIterator();
/**创建随机迭代器*/
public Iterator createRandomIterator();
/**获取指定位置的题目*/
public Question getQuestion(int index);
/**获取题目数量*/
public int getSize();
}
/**试卷*/
public class Paper implements IPaper{
/**题目列表*/
protected List<Question> questionList = new LinkedList<>();
@Override
public Iterator createDefaultIterator() {
return new DefaultIterator(this);
}
@Override
public Iterator createRandomIterator() {
return new RandomIterator(this);
}
public void addQuestion(Question question){
questionList.add(question);
}
public Question getQuestion(int index){
return questionList.get(index);
}
public int getSize(){
return questionList.size();
}
}
最后,让我们在看看客户端如何使用不同迭代器遍历试卷中的题目。
/**模拟遍历题目*/
public class Client {
public static void main(String[] args) {
Paper paper = new Paper();
//使用模式迭代器
Iterator defaultIterator = paper.createDefaultIterator();
while (defaultIterator.hasNext()){
Question question = defaultIterator.next();
appendToPDF(question);
}
//使用随机迭代器
Iterator randomIterator = paper.createRandomIterator();
while (randomIterator.hasNext()){
Question question = randomIterator.next();
appendToPDF(question);
}
}
}
结构
抽象迭代器角色(Iterator):它声明了遍历集合的操作。
具体迭代器角色(ConcreteIterator):它持有一个指向集合对象的引用,并实现了遍历集合的特定算法。
抽象聚合角色(Aggregate):它声明了创建具体迭代器的操作,这部分职责类似于迭代器的简单工厂。
具体聚合角色(ConcreteAggregate):它通常是一个集合,负责创建具体的迭代器。
总结
当集合对象底层的数据结构过于复杂,如果我们期望屏蔽客户端操作集合时的复杂度,那么我们应该考虑使用迭代器模式。
它可以解藕客户端和特定数据结构的依赖,这样即使集合底层数据结构变化,也不影响客户端。