迭代器模式(Iterator)

当需要提供一种方法顺序访问聚合对象中的元素,而无需暴露其内部表示时......

Posted by CloudingYu on April 11, 2025

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());
} 

优势分析

本例中迭代器模式实现的主要优势:

  1. 多种遍历方式:支持默认行优先遍历和自定义方向遍历,增强了灵活性

  2. 简化实现:通过匿名内部类实现迭代器接口,精简代码结构

  3. 良好封装
    • 迭代器维护自己的状态,不暴露棋盘内部表示
    • CheckBoard专注于状态管理,迭代逻辑封装在迭代器中
  4. 代码复用:使用Direction枚举定义移动方向,提高可读性和维护性

  5. 单一职责:遍历算法与集合结构分离,各自可独立变化

局限性分析

  1. 设计复杂度:对于简单集合可能造成不必要的复杂度

  2. 性能开销
    • 引入额外抽象层,带来少量性能损耗
    • 某些场景下无法达到直接索引访问的性能
  3. 状态管理
    • 需维护遍历状态,可能增加内存消耗
    • 集合修改时可能导致迭代器状态不一致
  4. 功能受限
    • 标准接口通常仅支持单向遍历
    • 对复杂遍历需求(如条件跳跃、回退)支持有限

总结

迭代器模式是一种简单而强大的设计模式,它通过将集合的遍历行为分离出来,而不暴露其内部结构的方法,实现了单一职责原则,使得集合和遍历算法可以独立变化。迭代器模式能为我们提供一种统一、简洁的方式来访问集合元素,同时保持良好的封装性和灵活性。在实际应用中,应当根据具体场景权衡是否使用这种模式。