迭代器模式

设计模式笔记:迭代器模式 (Iterator Pattern)

一、一句话概括

提供一种统一的方法顺序访问一个聚合对象(容器)中的各个元素,而无需暴露该对象的内部表示。

二、为什么需要它 (Why - The Pain Point)

我们有各种各样的数据集合,比如 ArrayList(基于数组)、LinkedList(基于链表)、HashSet(基于哈希表)、树、图等。每种数据结构的内部实现和元素存储方式都大相径庭。

痛点:
如果客户端代码想要遍历这些不同的集合,就必须为每一种集合编写不同的遍历逻辑。

1
2
3
4
5
6
7
8
9
10
// 痛点:客户端代码与集合的具体实现紧密耦合
// 遍历数组
for (int i = 0; i < array.length; i++) { ... }

// 遍历链表 (假设)
Node node = list.getHead();
while (node != null) {
// ... process node.getData() ...
node = node.getNext();
}

这种方式的问题是:

  1. 客户端代码复杂:客户端必须了解每种集合的内部数据结构。
  2. 耦合度高:客户端代码与 ArrayList, LinkedList 等具体集合类紧密耦合。如果将来想把 ArrayList 换成 LinkedList,所有遍历该集合的客户端代码都需要重写。
  3. 暴露内部实现:集合类被迫暴露其内部结构(如 getHead(), getNext()),这违反了封装原则。

核心问题:如何让客户端能够用一种统一、简单的方式遍历任何类型的集合,而无需关心集合的内部到底是怎么实现的?

三、它是什么 (What - The Solution)

迭代器模式将遍历的职责==从集合对象中分离出来,封装到一个独立的迭代器==(Iterator) 对象中。集合对象只负责创建并返回一个与其内部结构相匹配的迭代器实例。

核心思想和角色:

  1. **迭代器接口 (Iterator)**:
    • 定义了遍历元素所需的标准方法。在 Java 中,通常是 hasNext()next()。有时也包含 remove()
  2. **具体迭代器 (Concrete Iterator)**:
    • 实现了 Iterator 接口,负责对特定的聚合对象进行遍历。
    • 它内部需要持有一个对聚合对象的引用,并记录当前的遍历位置。
  3. **聚合接口 (Aggregate)**:
    • 定义了一个创建迭代器的方法,通常是 createIterator()iterator()
  4. **具体聚合 (Concrete Aggregate)**:
    • 实现了 Aggregate 接口,是具体的容器类(如 ArrayList, LinkedList)。
    • 它实现了 createIterator() 方法,返回一个适合自身结构的具体迭代器实例。

工作流程:
客户端想遍历一个 MyList 对象,它不直接操作 MyList,而是:

  1. 调用 myList.iterator(),获得一个 MyListIterator
  2. 使用 while (iterator.hasNext()) { ... iterator.next() ... } 的标准循环来处理元素。
    客户端完全不知道 MyList 内部是数组还是链表,它只与标准的 Iterator 接口交互。

四、怎么做 (How - The Blueprint)

Java 已经为我们做好了这一切! java.util.Iteratorjava.lang.Iterable 就是迭代器模式的完美实现。

  • java.lang.Iterable 对应聚合接口。它只包含一个方法 iterator()
  • java.util.Iterator 对应迭代器接口
  • 所有 java.util.Collection 的子类(如 ArrayList, LinkedList)都实现了 Iterable,并提供了自己的具体迭代器实现(通常作为私有内部类)。

Java 示例代码 (利用Java内置支持)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorPatternDemo {
public static void main(String[] args) {
// 创建一个具体聚合对象
List<String> menu = new ArrayList<>();
menu.add("Pizza");
menu.add("Burger");
menu.add("Pasta");

// --- 传统方式使用迭代器 ---
System.out.println("--- Using traditional iterator ---");
// 1. 从聚合对象获取迭代器
Iterator<String> iterator = menu.iterator();

// 2. 使用统一的循环方式遍历
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}

// --- 更现代的方式:for-each 循环 ---
// for-each 循环是迭代器模式的语法糖!
// 编译器会自动将 for-each 转换为上面的 while 循环代码
System.out.println("\n--- Using for-each loop (syntactic sugar) ---");
for (String item : menu) {
System.out.println(item);
}

// 迭代器还支持在遍历时安全地删除元素
Iterator<String> removeIterator = menu.iterator();
while (removeIterator.hasNext()) {
String item = removeIterator.next();
if (item.equals("Burger")) {
removeIterator.remove(); // 正确的删除方式
}
}
System.out.println("\n--- Menu after removing Burger ---");
System.out.println(menu);
}
}

五、UML 类图 (Java Collection Framework)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@startuml
skinparam classAttributeIconSize 0
hide empty members

title Iterator Pattern Demo - UML Diagram

' 接口定义
interface Iterable<T> {
+ Iterator<T> iterator()
}

interface Iterator<T> {
+ boolean hasNext()
+ T next()
+ void remove()
}

interface List<T> extends Iterable<T>

' 抽象类定义
abstract class AbstractList<T> implements List<T>

' 具体类
class ArrayList<T> extends AbstractList<T>

' 客户端代码类
class IteratorPatternDemo {
+ main(String[]): void
}

' 关系连接
List <|-- AbstractList
AbstractList <|-- ArrayList
Iterable <|-- List
Iterable <|.. ArrayList
ArrayList --> Iterator : iterator()
IteratorPatternDemo --> ArrayList : uses
IteratorPatternDemo --> Iterator : uses

@enduml

六、优缺点分析

优点 (Pros):

  1. 封装和解耦:完美地将集合的内部结构与遍历行为解耦,客户端无需了解集合的实现细节。
  2. 统一的遍历接口:为所有类型的集合提供了统一的遍历方式,简化了客户端代码。
  3. 支持多种遍历方式:一个集合可以提供多种不同的迭代器(如正向、反向、跳跃等)。
  4. 安全地修改:迭代器的 remove() 方法是遍历时修改集合的唯一安全方式,避免了并发修改异常 (ConcurrentModificationException)。
  5. 遵循单一职责原则:将遍历的逻辑从集合中移出,让集合专注于存储,迭代器专注于遍历。

缺点 (Cons):

  1. 增加类的数量:对于简单的集合,单独创建一个迭代器类可能会显得有点过度设计。但在 Java 中,这通常通过轻量级的内部类来解决,所以影响不大。