设计模式01——Iterator模式
- 2020 年 4 月 3 日
- 筆記
定义
迭代器(Iterator
)模式,从其英文单词可以看出,有反复做某件事的意思。迭代器模式常用于数据集合中,对数据集合中的数据按照顺序进行遍历。它能提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节。
问题引入
假设我们有一个书架,假设书架可以按照顺序放置无数本书,现在有一个需求,那就是遍历书架里面的所有的书籍,将书籍的名称打印出来。

常规的解决办法
一般情况下,我们在用代码来解决该问题的时候,都是想到使用for
循环,对书架中的每一本书进行循环遍历,然后输出名称。这是一种比较常规的做法,考虑的问题就是为了遍历书架中的书籍,并使用变量i
来记录当前遍历到哪一本书了,以及将指针指向下一本书,伪代码如下:
List<Book> books = new ArrayList<>(); books.add(new Book("深入理解Java虚拟机")); books.add(new Book("深入分析Java Web技术内幕")); books.add(new Book("Java编程思想")); books.add(new Book("Linux就该这么学")); for (int i = 0; i < books.size(); i++) { System.out.println(books.get(i).getName()); }
这里的for
语句中的i++
的作用是将i
的值在每一次循环之后就自增1
,使得在遍历的过程中就可以访问集合中的下一本书籍,下下一本书籍,再下下一本书籍,直到遍历完所有的书籍。如果我们考虑将i
的作用进行抽象化,使其通用化,那么就可以形成迭代器模式。
从List中获取启发
说到迭代器设计模式,大家肯定会想到java.util
包中的集合,是的,Java
中集合类中运用到了迭代器设计模式,那么,我们在学习迭代器设计模式的时候,完全可以通过去学习集合的源码来学习迭代器设计模式,下面,我将从ArrayList
出发,探究一下在ArrayList
中如何运用的迭代器设计模式。 作为集合类的顶级接口,Collection
接口继承了Iterable
接口,Iterable
接口的子接口或者实现类都具备迭代和遍历的功能,那么List接口继承自Collection
,自然也是具备基本的迭代功能的,那么我们从List
出发,来探究迭代器模式的运用。 List
接口中有一个重写自Collection
的iterator()
方法,它的返回值是一个Iterator
接口的实现类对象。

那么我们接着看ArrayList
类,它实现了List
接口,也实现了List
接口中的iterator()
方法。

那么,针对实例化ArrayList
的时候传入的具体的泛型,可以生成其对应的迭代器,也就是说,上图中new
语句后面的Itr()
创造出来的迭代器对象肯定是针对传入的泛型的迭代器对象。我们继续阅读代码,发现Itr
是ArrayList
的私有内部类,它还实现了Iterator
接口,也实现了基本的hasNext()
方法和next()
方法。

因为Itr
是ArrayList
的内部类,那么Itr
可以操作ArrayList
的成员变量,而ArrayList
作为集合,它内部的元素是存储在变量名为elementData
的Object
数组中的,整个遍历的过程就是针对这个数组进行遍历的。 分析完源码,我们也许还是有些迷糊,那么我们需要借助UML类图来描述这些接口或者类之间的关系了。

从上图可以看出,List
接口中有创建Iterator
接口的实现类对象的抽象方法,而ArrayList
实现了List
接口,而Itr
类实现了Iterator
接口,且其拥有ArrayList
的元素数组,可以针对该数组进行一系列的操作,比如遍历。类图可以很清晰表表现出类与类之间的关系,不太熟悉的可以去学习一下UML
类图。
手动实现迭代器设计模式
从List
中获取到迭代器设计模式实现的灵感,那么我们需要自己手动实现自己的迭代器模式,来解决文章开始引入的遍历书架的问题。首先,我们根据List
中采用的迭代器设计模式,我们需要一个集合接口Aggregate
(有“使聚集”、“聚合”的意思),该集合接口中有创建迭代器接口Iterator
实现类对象的抽象方法iterator
,这个迭代器接口有hasNext
、next
方法,用来判断集合是否还有元素以及获取集合元素,我们还需要具体的集合实现类,也就是具体的书架,用来承载书籍,该实现类实现了Aggregate
集合的iterator
方法,最后,我们还需要实现迭代器接口的具体类,它持有集合的具体的实现类,还实现了hasNext
和next
方法。我们将类图设计如下所示:

根据类图,我们可以很轻松地设计出基本的迭代器设计模式及的代码,主要代码如下所示:
- 集合接口Aggregate
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 是需要遍历的集合的接口 * * @author jiangpingping * @date 2018/8/27 下午9:31 */ public interface Aggregate<E> { /** * 返回当前集合的迭代器 * * @return 当前集合的迭代器 */ Iterator<E> iterator(); }
该接口遵循类图的设计,接口中包含创建Iterator
接口实现类对象的方法。
- 迭代器接口Iterator
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 迭代器接口 * * @author jiangpingping * @date 2018/8/27 下午9:32 */ public interface Iterator<E> { /** * 检测是否有下一个元素 * * @return 如果有,返回true,否则返回false */ boolean hasNext(); /** * 返回当前元素,并将指针指向下一个元素 * * @return 当前元素 */ E next(); }
该迭代器接口有两个方法,分别是判断被遍历的集合是否还有元素以及获取集合中元素并将指针指向下一个元素。
- 具体的集合类BookShelf
package cn.itlemon.design.pattern.chapter01.iterator.example; import java.util.ArrayList; /** * 书架,其实就是承载书籍的集合或数组 * * @author jiangpingping * @date 2018/8/27 下午9:42 */ public class BookShelf<E> implements Aggregate { private ArrayList<E> books; private int last = 0; public BookShelf(int initSize) { books = new ArrayList<>(initSize); } public E getBookAt(int index) { return books.get(index); } public void appendBook(E e) { books.add(e); last++; } public int getLength() { return last; } @Override public Iterator<E> iterator() { return new BookShelfIterator<>(this); } }
getBookAt
方法可以获取指定索引位置的书籍,appendBook
方法是向书架中最后一个位置添加书籍的方法,getLength
方法可以得到书籍的数量,iterator
方法可以获取当前书架的迭代器实现类对象。
- 迭代器实现类BookShelfIterator
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * {@link BookShelf} 的迭代器 * * @author jiangpingping * @date 2018/8/27 下午9:49 */ public class BookShelfIterator<E> implements Iterator<E> { private BookShelf<E> bookShelf; private int index; public BookShelfIterator(BookShelf<E> bookShelf) { this.bookShelf = bookShelf; index = 0; } @Override public boolean hasNext() { return index < bookShelf.getLength(); } @Override public E next() { E e = bookShelf.getBookAt(index); index++; return e; } }
hasNext
方法可以判断遍历的集合中是否还有未遍历的元素,next
方法直接获取元素,并将在指针指向下一个元素,所以在使用List
的迭代器的时候,要特别留意next
方法,在每次循环的过程中,next
方法只能使用一次,多次使用的话,单次遍历会引起逻辑错误。
- 书籍类Book
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 书籍的实体类 * * @author jiangpingping * @date 2018/8/27 下午9:39 */ public class Book { private String name; public Book(String name) { this.name = name; } public String getName() { return name; } }
- 测试类Main
package cn.itlemon.design.pattern.chapter01.iterator.example; /** * 测试Iterator模式的主类 * * @author jiangpingping * @date 2018/8/27 下午10:01 */ public class Main { public static void main(String[] args) { BookShelf<Book> bookShelf = new BookShelf<>(4); bookShelf.appendBook(new Book("深入理解Java虚拟机")); bookShelf.appendBook(new Book("深入分析Java Web技术内幕")); bookShelf.appendBook(new Book("Java编程思想")); bookShelf.appendBook(new Book("Linux就该这么学")); Iterator<Book> iterator = bookShelf.iterator(); while (iterator.hasNext()) { Book book = iterator.next(); System.out.println(book.getName()); } } }
看到上面的测试主类,和使用ArrayList
的迭代器一模一样,也就是说,我们手动实现了迭代器设计模式,其实,在设计过程中,所有的泛型都是使用E
来进行代替的,所以这些类不仅仅适用Book
的遍历,支持其他类的遍历,如果在将类的名称修改一下,改成更加通用的,那么这个迭代器就将适合更多的集合遍历,遍历的元素可以是其他的类对象。
浅析迭代器模式中的重要角色
任何模式都是前人积累下来的经验,大多数模式中都有固定的角色,使用模式的时候,可以根据固定的角色来编写代码,就可以轻松地利用上设计模式,使自己的代码更具有“可复用性”。接下来将浅析迭代器设计模式中的重要角色。
- Iterator(迭代器接口) 该角色负责定义按顺序逐个遍历元素的接口(
API
),在本次示例中,由Iterator
接口扮演了这个角色,它定义了两个方法,hasNext
和next
方法,分别是判断被遍历的集合或者数组是否还有下一个元素以及直接获取元素,并将指针指向下一个元素。 - ConcreteIterator(具体的迭代器) 该角色实现了
Iterator
所定义的接口(API
),在本次示例中,由BookShelfIterator
类来扮演了这个角色,它持有需要遍历的数组或者集合的具体信息,也就是说需要被遍历的集合存在于这个迭代器中。 - Aggregate(集合) 该角色负责定义创建
Iterator
角色的接口(API
),这个接口会创建出一个Iterator
的实现类对象,这个实现类对象可以对实现Aggregate
集合接口类对象进行遍历。本次示例中,由Aggregate
接口来扮演这个角色。 - ConcreteAggregate(具体的集合) 该角色负责实现Aggregate所定义的接口(
API
),它会创建出具体的Iterator
角色,也就是ConcreteIterator
角色。在本次示例中,由BookShelf
扮演了这个角色,它实现类iterator
方法。
迭代器设计模式UML类图

为什么要使用迭代器设计模式
为什么在遍历的时候要额外引入迭代器这种复杂的模式呢?
也许你有这样的疑问,引入迭代器设计模式的一个重要理由是:将实现和遍历进行了分离,也就是说遍历的过程完全不依赖与你所选择的集合是如何实现的,在示例中,使用的ArrayList
来承载数据的,如果开发者换了其他的容器来承载数据,那么只需要修改集合的实现方式即可,完全不需要去修改遍历的过程代码,这就提高了代码的“可复用性”和“可靠性”。