Iterator 模式,又称迭代器模式,它提供了一种方法来访问一个聚合对象中的各个元素,而无需暴露该对象的内部表示。这种模式将迭代逻辑从集合中分离出来,放到迭代器对象中,使得我们可以为不同的集合结构实现不同的迭代方式,且不会暴露集合的内部结构。
基本结构
参与者
在 Iterator 模式中,我们可以抽象出两个参与者
-
Iterator
定义了访问和遍历元素的接口,提供了统一的方法来遍历聚合对象中的元素,而无需暴露聚合对象的内部表示。
-
Aggregate
表示一个包含多个元素的容器,如列表、集合等。必须提供一个创建迭代器对象的方法,用于返回一个符合迭代器接口的对象。
类图结构
Iterator
: 迭代器接口,定义访问和遍历元素的标准方法。主要包含判断是否有下一个元素的hasNext()
方法和获取下一个元素的next()
方法,提供了一种统一的方式来顺序访问集合中的元素。isIterable
: 可迭代对象接口,定义了创建迭代器的方法。实现此接口的类必须提供一个createIterator()
方法,返回一个可以用来遍历该对象元素的迭代器实例。ConcreteIterator
: 具体迭代器类,实现Iterator
接口。负责跟踪当前遍历的位置,并提供访问集合中元素的具体实现。它维护遍历的状态,知道哪些元素已经被遍历,哪些元素还没有被遍历。ConcreteAggregate
: 具体聚合类,实现isIterable
接口。它是一个包含多个元素的容器对象,如列表、数组等。它实现createIterator()
方法来返回一个能够遍历其内部元素的具体迭代器实例,同时隐藏内部数据结构的具体实现细节。
Java标准库中的迭代器
在Java中,迭代器模式已经被标准化为 java.util.Iterator
接口和 java.lang.Iterable
接口。实现了 Iterable
接口的对象可以使用 for-each
语法进行迭代,使用起来非常方便。
以下是Java标准库中迭代器的基本接口:
1
2
3
4
5
6
7
8
9
10
11
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
public interface Iterable<T> {
Iterator<T> iterator();
}
实例演示
问题
假设一个棋类游戏,黑白两色的棋子,在棋盘内部,棋子是用一个二维数组来存储。需要对棋盘中的棋子进行遍历:
解决方案 1(不使用迭代器模式)
直接通过两层循环实现对每一个棋盘格的遍历。这种方法的缺点是:
- 需要了解对象的具体结构才能访问
- 如果结构发生变化,需要修改所有相关代码
1
2
3
4
5
6
7
8
public class CheckBoardManager {
public void traverse(CheckBoard checkerBoard) {
// 通过两层循环依次遍历每一个棋盘格
for (int y = 0; y < checkerBoard.getMax_y(); y++)
for (int x = 0; x < checkerBoard.getMax_x(); x++)
visit(checkerBoard.getChess(x, y));
}
}
解决方案 2(使用迭代器模式)
以下代码已省略构造器及取值器
定义方向枚举与点类,用于定向遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Point {
int x;
int y;
}
enum Direction {
up(new Point(0, -1)),
down(new Point(0, 1)),
left(new Point(-1, 0)),
right(new Point(1, 0)),
upLeft(new Point(-1, -1)),
upRight(new Point(1, -1)),
downRight(new Point(1, 1)),
downLeft(new Point(-1, 1));
private Point p;
}
实现棋盘类及迭代器
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 棋盘类,实现可迭代接口
public class CheckBoard implements Iterable<Cell> {
private ChessType[][] chessBoard;
private int max_x;
private int max_y;
// 构造默认迭代器
@Override
public Iterator<Cell> iterator() {
return new Iterator<Cell>() {
private int x = 0;
private int y = 0;
@Override
public boolean hasNext() {
// 判断棋盘上是否有下一个棋子
return x < getMax_x() && y < getMax_y();
}
@Override
public Cell next() {
// 获取当前坐标上的棋子,并移动到下一个位置
Cell cell = new Cell(x, y, getChess(x, y));
x++; // 在x轴上移动到下一个位置
if (x >= getMax_x()) {
// 如果x轴达到最大值,则重置x轴并移动y轴到下一个位置
x = 0;
y++;
}
return cell;
}
};
}
// 构造自定义迭代器,用于按指定方向和起始点遍历
@Override
public Iterator<Cell> iterator(Direction dir, Point start) {
return new Iterator<Cell>() {
private Point p = start;
@Override
public boolean hasNext() {
// 检查当前位置p是否在棋盘的有效范围内
return p.getX() < getMax_x() && p.getY() < getMax_y()
&& p.getX() >= 0 && p.getY() >= 0;
}
@Override
public Cell next() {
// 根据当前位置p创建一个棋盘格对象
Cell cell = new Cell(p.getX(), p.getY(), getChess(p.getX(), p.getY()));
// 根据给定的方向更新当前位置p
p = new Point(p.getX() + dir.getP().getX(), p.getY() + dir.getP().getY());
return cell;
}
};
}
}
使用迭代器进行遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 调用默认迭代器进行遍历
public void traverse(CheckBoard checkerBoard) {
Iterator<Cell> cellIterator = checkerBoard.iterator();
while (cellIterator.hasNext())
visit(cellIterator.next());
}
// 使用 for-each 进行遍历
public void traverseWithForEach(CheckBoard checkerBoard) {
for (Cell cell : checkerBoard) {
visit(cell);
}
}
// 自定义方向和起始点进行遍历
public void traverseWithDir(CheckBoard checkerBoard) {
Iterator<Cell> i = checkerBoard.iterator(Direction.down, new Point(2, 2));
while (i.hasNext())
visit(i.next());
}
优势分析
本例中迭代器模式实现的主要优势:
-
多种遍历方式:支持默认行优先遍历和自定义方向遍历,增强了灵活性
-
简化实现:通过匿名内部类实现迭代器接口,精简代码结构
- 良好封装:
- 迭代器维护自己的状态,不暴露棋盘内部表示
CheckBoard
专注于状态管理,迭代逻辑封装在迭代器中
-
代码复用:使用
Direction
枚举定义移动方向,提高可读性和维护性 - 单一职责:遍历算法与集合结构分离,各自可独立变化
局限性分析
-
设计复杂度:对于简单集合可能造成不必要的复杂度
- 性能开销:
- 引入额外抽象层,带来少量性能损耗
- 某些场景下无法达到直接索引访问的性能
- 状态管理:
- 需维护遍历状态,可能增加内存消耗
- 集合修改时可能导致迭代器状态不一致
- 功能受限:
- 标准接口通常仅支持单向遍历
- 对复杂遍历需求(如条件跳跃、回退)支持有限
总结
迭代器模式是一种简单而强大的设计模式,它通过将集合的遍历行为分离出来,而不暴露其内部结构的方法,实现了单一职责原则,使得集合和遍历算法可以独立变化。迭代器模式能为我们提供一种统一、简洁的方式来访问集合元素,同时保持良好的封装性和灵活性。在实际应用中,应当根据具体场景权衡是否使用这种模式。