前言
在软件开发中,集合对象的遍历是一项常见且关键的操作。如何在不暴露集合内部结构的前提下,灵活、高效地遍历集合元素?迭代器模式(Iterator Pattern)为这一问题提供了完美的解决方案。作为一种行为型设计模式,迭代器模式将集合的遍历逻辑与集合本身分离,使得遍历操作可以独立于集合的具体实现而存在。本文将从迭代器模式的定义出发,深入剖析其核心结构、实现方式、优缺点及实际应用场景,帮助开发者全面理解并灵活运用这一经典设计模式。
定义
迭代器模式的核心定义可概括为:提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。这一定义揭示了迭代器模式的本质 ------ 在封装集合内部结构的同时,提供统一的元素访问接口。
从职责角度看,迭代器模式清晰地划分了两项核心职责。第一项是存储数据,这是聚合对象的基本职责,聚合对象负责管理元素的存储与组织,如数组、链表、树等不同的数据结构都承担着这一职责。第二项是遍历数据,这一职责具有双重特性:既是可变化的,不同的业务场景可能需要不同的遍历方式;又是可分离的,遍历逻辑可以与聚合对象本身相分离,形成独立的迭代器组件。这种职责分离正是迭代器模式的精髓所在,它使得聚合对象专注于数据存储,而迭代器专注于数据遍历,两者通过抽象接口进行交互,实现了解耦。
在实际开发中,这种职责划分带来了显著的优势。例如,当需要修改集合的遍历方式时,只需调整迭代器的实现而无需改动集合本身;当集合的内部结构发生变化时,只要迭代器的接口保持稳定,客户端代码就无需进行修改。这种分离使得系统的各个组件更加专注于自身的核心功能,提高了代码的可维护性和可扩展性。
角色
迭代器模式通过清晰的角色划分实现了集合与遍历的分离,其核心角色包括抽象容器、具体容器、抽象迭代器和迭代器实现。这些角色相互协作,共同构成了迭代器模式的完整架构。
抽象容器(Aggregate)
抽象容器通常是一个接口,它定义了聚合对象的基本行为,其中最关键的是提供一个iterator()方法。这个方法的作用是返回一个用于遍历该聚合对象的迭代器实例。抽象容器的存在为所有具体容器提供了统一的接口规范,使得客户端可以通过抽象接口来操作不同的具体容器,而无需关心其具体实现。在 Java 集合框架中,Collection接口就是典型的抽象容器,它定义了iterator()方法,为所有实现类提供了统一的迭代器获取方式。
具体容器(ConcreteAggregate)
具体容器是抽象容器的具体实现类,它负责实现抽象容器中定义的方法,特别是iterator()方法,该方法返回一个与该具体聚合对应的具体迭代器ConcreteIterator实例。不同的具体容器可以采用不同的数据结构来存储元素,例如List接口的有序列表实现ArrayList采用动态数组存储元素,而链表实现LinkedList则采用双向链表存储元素。尽管它们的内部结构不同,但通过实现iterator()方法,都能为客户端提供适合自身的迭代器,从而实现元素的遍历。
抽象迭代器(Iterator)
抽象迭代器定义了遍历元素所需要的方法,这些方法是客户端与迭代器进行交互的核心接口。通常包括取得第一个元素的方法first()、取得下一个元素的方法next()、判断是否遍历结束的方法hasNext()、移除当前对象的方法remove()以及用于获取当前元素的currentItem()等。抽象迭代器的存在使得客户端可以通过统一的接口来遍历不同的聚合对象,而无需关心具体的迭代实现。
迭代器实现(ConcreteIterator)
迭代器实现类负责实现抽象迭代器接口中定义的方法,完成对具体聚合对象的迭代操作。在迭代过程中,它需要跟踪当前遍历的位置,以便正确地获取下一个元素、判断遍历是否结束等。不同的具体容器通常对应不同的迭代器实现,因为它们的内部数据结构不同,遍历方式也会有所差异。例如,ArrayList的迭代器可以通过索引来快速访问元素,而LinkedList的迭代器则需要通过指针来遍历链表节点。
类图

为了更直观地理解这些角色之间的关系,我们可以通过类图来进行展示。在类图中,抽象容器(Aggregate)与抽象迭代器(Iterator)之间存在关联关系,抽象容器通过iterator()方法返回抽象迭代器的实例。具体容器(ConcreteAggregate)继承自抽象容器,并实现了iterator()方法,返回具体迭代器(ConcreteIterator)的实例。具体迭代器则实现了抽象迭代器接口,并与具体容器之间存在关联关系,以便访问具体容器中的元素进行遍历。这种类图结构清晰地展现了迭代器模式中各个角色的协作方式,为我们理解和实现迭代器模式提供了重要的指导。
优缺点
优点
迭代器模式的优点主要体现在以下几个方面:
- 简化了遍历方式:迭代器模式将集合的遍历逻辑封装在迭代器中,客户端只需通过迭代器提供的接口(如hasNext()和next()方法)即可完成对集合的遍历,无需了解集合的内部结构和遍历细节。这种简化使得客户端代码更加简洁、清晰,降低了开发难度。例如,在遍历ArrayList时,客户端无需关心其内部的动态数组结构,只需通过迭代器的方法就能依次获取元素。
- 可以提供多种遍历方式遍历一个聚合对象:对于同一个聚合对象,我们可以根据不同的需求提供多种不同的迭代器实现,从而实现不同的遍历方式。例如,对于一个有序列表,我们可以提供正向遍历的迭代器和反向遍历的迭代器;对于一个树形结构的聚合对象,我们可以提供前序遍历、中序遍历和后序遍历等多种迭代器。这种灵活性使得聚合对象能够满足不同场景下的遍历需求。
- 封装性良好,符合开闭原则:迭代器模式引入了抽象层(抽象容器和抽象迭代器),使得具体容器和具体迭代器之间相互解耦。当需要增加新的聚合类时,只需实现抽象容器接口并提供相应的具体迭代器即可;当需要增加新的遍历方式时,只需创建新的具体迭代器实现类即可。这种设计使得系统具有良好的扩展性,符合开闭原则(对扩展开放,对修改关闭)。
缺点
迭代器模式也存在一些不足之处,需要在使用过程中加以注意:
- 对于简单遍历,使用迭代器较为繁琐:在一些简单的场景中,例如遍历一个数组或一个小型的集合,直接通过循环语句进行遍历可能更加简洁、高效。而使用迭代器模式则需要创建迭代器对象、调用迭代器方法等一系列操作,显得有些多余和繁琐。
- 设计难度较大,抽象迭代器不易创建:迭代器模式需要设计抽象容器、具体容器、抽象迭代器和具体迭代器等多个角色,涉及到抽象层的设计和具体实现的协调。在自定义迭代器时,需要充分考虑到不同聚合结构的特点、遍历方式的多样性以及系统将来的扩展需求,创建一个考虑全面的抽象迭代器并非易事。如果抽象迭代器设计不合理,可能会导致后续的扩展困难或迭代器功能不完善。
使用场景
迭代器模式适用于多种需要对聚合对象进行遍历的场景,了解这些使用场景可以帮助我们在实际开发中正确地选择和应用迭代器模式。
实现集合类时必须提供迭代器
在软件开发中,只要实现一个集合类,就需要同时提供该集合的迭代器,这是迭代器模式最基本的使用场景。就像 Java 中的Collection、List、Set、Map等集合接口,它们都定义了获取迭代器的方法,其具体实现类也都提供了相应的迭代器实现。这是因为集合类的核心功能之一就是存储和管理元素,而遍历元素是使用集合类时最常见的操作之一。通过提供迭代器,集合类可以将遍历逻辑与自身的存储逻辑分离,提高代码的可维护性和可扩展性。
需访问聚合对象内容而不暴露内部表示
当需要访问一个聚合对象的内容,但又不希望暴露其内部表示时,迭代器模式是一个理想的选择。聚合对象的内部表示可能涉及到复杂的数据结构、算法或敏感信息,直接暴露给客户端会带来安全风险,也会增加客户端对聚合对象内部结构的依赖。通过迭代器,客户端可以通过统一的接口访问聚合对象中的元素,而无需了解聚合对象是如何存储和组织这些元素的。例如,在一个封装了数据库查询结果的聚合对象中,我们可以通过迭代器让客户端遍历查询结果,而无需暴露数据库连接、查询语句等内部细节。
需要为聚合对象提供多种遍历方式
当一个聚合对象需要支持多种不同的遍历方式时,迭代器模式可以发挥重要作用。不同的遍历方式可能适用于不同的业务场景,例如对于一个订单列表,有时需要按照订单日期正序遍历,有时需要按照订单金额倒序遍历,有时需要只遍历已付款的订单。通过为聚合对象提供不同的具体迭代器实现,我们可以轻松地实现这些不同的遍历方式,而无需修改聚合对象本身。客户端可以根据自己的需求选择合适的迭代器进行遍历,提高了系统的灵活性。
为不同聚合结构提供统一遍历接口
当需要为遍历不同的聚合结构提供一个统一的接口时,迭代器模式可以实现这一目标。不同的聚合结构可能具有完全不同的内部实现,例如数组、链表、树、图等,但通过迭代器模式,我们可以为它们定义一个统一的抽象迭代器接口。在该接口的实现类中,为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口,无需关心具体的聚合结构和遍历实现。这种统一的接口使得客户端代码更加通用,能够在不同的聚合结构之间进行无缝切换。
使用案例
Java Collections
Java Collections 框架是迭代器模式在实际开发中应用的典型案例,它充分体现了迭代器模式的设计思想和优势。JDK 1.2 引入了新的 Java 聚合框架 Collections,其中Collection是所有 Java 聚合类的根接口,它为 Java 中的集合类提供了统一的规范和接口。
在 JDK 类库中,Collection接口定义了iterator()方法,该方法返回一个java.util.Iterator类型的对象。Iterator接口就是迭代器模式中的抽象迭代器,它定义了遍历元素所需要的基本方法。Iterator接口具有如下 3 个基本方法:
- Object next():通过反复调用next()方法可以逐个访问聚合中的元素,每次调用该方法都会返回聚合中的下一个元素。
- boolean hasNext():判断是否还有未遍历的元素,如果还有元素可供遍历,则返回true,否则返回false。
- void remove():移除当前迭代器所指向的元素,该方法的使用需要谨慎,因为它可能会影响聚合对象的结构。
Collection的子接口java.util.List还提供了listIterator()方法,该方法返回一个java.util.ListIterator类型的对象。ListIterator是Iterator的子类,它在Iterator的基础上增加了更多的功能,例如可以向前遍历元素(previous()方法)、获取当前元素的索引(nextIndex()和previousIndex()方法)以及修改元素(set()方法)等。ListIterator的出现使得List集合的遍历更加灵活多样。
在 JDK 的具体实现中,ArrayList、LinkedList等List接口的实现类都提供了相应的迭代器实现。例如,ArrayList的迭代器通过索引来访问元素,利用数组的随机访问特性实现高效的遍历;LinkedList的迭代器则通过指针来遍历链表节点,适应链表的顺序访问特性。这些具体的迭代器实现隐藏了集合的内部结构,为客户端提供了统一的遍历接口,使得客户端可以以相同的方式遍历不同的List实现类,而无需关心它们的内部存储方式。
Java Collections 框架对迭代器模式的应用,不仅简化了集合的遍历操作,提高了代码的可维护性和可扩展性,还使得不同集合之间的遍历操作具有一致性,降低了客户端的使用难度。这一案例充分证明了迭代器模式在大型框架设计中的重要价值和广泛应用前景。
代码实现示例
为了更好地理解迭代器模式的工作原理,我们通过一个具体的代码示例来展示迭代器模式的实现过程。假设我们需要设计一个图书集合类,用于存储图书信息,并提供相应的迭代器来遍历图书集合。
-
定义抽象容器(Aggregate):首先,我们定义一个抽象容器接口BookCollection,它声明了添加图书、获取图书数量以及获取迭代器的方法。
public interface BookCollection {
void addBook(Book book);
int getBookCount();
Book getBook(int index);
Iterator createIterator();
} -
定义具体容器(ConcreteAggregate):接下来,我们实现抽象容器接口,创建一个具体的图书集合类ConcreteBookCollection。该类采用数组来存储图书信息,并实现了BookCollection接口中的方法。
public class ConcreteBookCollection implements BookCollection {
private Book[] books;
private int size;public ConcreteBookCollection(int capacity) { books = new Book[capacity]; size = 0; } @Override public void addBook(Book book) { if (size < books.length) { books[size++] = book; } else { System.out.println("图书集合已满,无法添加新图书。"); } } @Override public int getBookCount() { return size; } @Override public Book getBook(int index) { if (index >= 0 && index < size) { return books[index]; } return null; } @Override public Iterator createIterator() { return new BookIterator(this); }
}
-
定义图书类(Book):我们创建一个简单的Book类来表示图书信息,包含图书的名称和作者属性。
public class Book {
private String name;
private String author;public Book(String name, String author) { this.name = name; this.author = author; } public String getName() { return name; } public String getAuthor() { return author; } @Override public String toString() { return "Book{name='" + name + "', author='" + author + "'}"; }
}
-
定义抽象迭代器(Iterator):抽象迭代器接口Iterator定义了遍历图书集合所需要的方法。
public interface Iterator {
boolean hasNext();
Object next();
Object currentItem();
} -
定义迭代器实现(ConcreteIterator):我们实现抽象迭代器接口,创建BookIterator类,用于遍历ConcreteBookCollection中的图书。
public class BookIterator implements Iterator {
private BookCollection bookCollection;
private int currentIndex;public BookIterator(BookCollection bookCollection) { this.bookCollection = bookCollection; currentIndex = 0; } @Override public boolean hasNext() { return currentIndex < bookCollection.getBookCount(); } @Override public Object next() { Book book = bookCollection.getBook(currentIndex); currentIndex++; return book; } @Override public Object currentItem() { return bookCollection.getBook(currentIndex); }
}
-
客户端代码(Client):客户端通过抽象接口来使用迭代器模式,实现对图书集合的遍历。
public class Client {
public static void main(String[] args) {
// 创建图书集合
BookCollection bookCollection = new ConcreteBookCollection(5);
// 添加图书
bookCollection.addBook(new Book("《设计模式》", " Erich Gamma"));
bookCollection.addBook(new Book("《Java编程思想》", "Bruce Eckel"));
bookCollection.addBook(new Book("《深入理解Java虚拟机》", "周志明"));
// 获取迭代器
Iterator iterator = bookCollection.createIterator();
// 遍历图书集合
System.out.println("遍历图书集合:");
while (iterator.hasNext()) {
Book book = (Book) iterator.next();
System.out.println(book);
}
}
} -
BookCollection是抽象容器,定义了图书集合的基本操作和获取迭代器的方法。
-
ConcreteBookCollection是具体容器,实现了BookCollection接口,使用数组存储图书,并通过createIterator()方法返回BookIterator实例。
-
Iterator是抽象迭代器,定义了hasNext()、next()和currentItem()方法。
-
BookIterator是具体迭代器,实现了抽象迭代器的方法,通过跟踪当前索引currentIndex来实现对图书集合的遍历。
在遍历阶段,客户端通过while (iterator.hasNext())循环判断是否还有未遍历的元素。并循环内部调用iterator.next()方法获取下一本图书。整个过程中,客户端始终通过Iterator接口与迭代器交互,无需了解ConcreteBookCollection内部的数组存储结构,实现了对集合内部表示的隐藏。