十四、迭代器模式


1 基本介绍

迭代器模式(Iterator Pattern)是一种 行为型 设计模式,它 提供了一种方法来按照某种顺序访问集合中的各个元素而不需要暴露其内部表示

2 案例

本案例实现了一个自定义的 MyArray 类,它可以存放指定数量的 Object 类型的对象,然后通过 MyArrayIterator 类的方法来获取并使用其中储存的元素。

2.1 Aggregate 接口

public interface Aggregate { // 集合接口
    boolean isEmpty(); // 检查集合是否为空

    boolean isFull(); // 检查集合是否已满

    Iterator iterator(); // 返回用于遍历集合的迭代器

    boolean add(Object obj); // 添加新元素,如果集合已满,则添加失败;否则添加成功

    int size(); // 返回集合中的元素个数
}

2.2 Iterator 接口

public interface Iterator { // 迭代器接口
    boolean hasNext(); // 判断是否存在下一个元素

    Object next(); // 获取下一个元素,同时将迭代器移动到下一个元素的位置
}

2.3 MyArray 类

public class MyArray implements Aggregate {
    private Object[] array; // 存放元素的数组
    private int size; // 当前数组中的元素个数

    public MyArray(int initSize) {
        array = new Object[initSize];
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean isFull() {
        return size == array.length;
    }

    @Override
    public Iterator iterator() {
        return new MyArrayIterator(this);
    }

    @Override
    public boolean add(Object obj) {
        if (isFull()) {
            return false;
        }
        array[size++] = obj;
        return true;
    }

    @Override
    public int size() {
        return size;
    }

    // 获取索引为 index 的元素,用于 MyArrayIterator 类的 next(),不应该被外部访问到
    protected Object get(int index) {
        return array[index];
    }
}

2.4 MyArrayIterator 类

public class MyArrayIterator implements Iterator {
    private MyArray myArray; // 待遍历的集合
    private int curr; // 当前元素索引,也就是迭代器

    public MyArrayIterator(MyArray myArray) {
        this.myArray = myArray;
    }

    @Override
    public boolean hasNext() {
        return curr < myArray.size();
    }

    @Override
    public Object next() {
        Object obj = myArray.get(curr); // 获取下一个元素
        curr++; // 将迭代器移动到下一个元素的位置
        return obj;
    }
}

2.5 Client 类

public class Client { // 客户端,测试 MyArray 和 MyArrayIterator
    public static void main(String[] args) {
        Aggregate myArray = new MyArray(5);
        myArray.add("Hello, World!");
        myArray.add(2.0);
        myArray.add(3000);
        myArray.add(true);
        myArray.add('c');

        Iterator iterator = myArray.iterator();
        while (iterator.hasNext()) { // 如果集合中有剩余元素
            Object curr = iterator.next(); // 获取它
            System.out.println(curr); // 使用它
        }
    }
}

2.6 Client 类的运行结果

Hello, World!
2.0
3000
true
c

2.7 总结

通过 MyArrayIterator 迭代器的 hasNext(), next()Client 客户端无需知道 MyArray 集合的内部实现,只需要会使用这两个方法即可,这就 降低了客户端与集合之间的耦合性增强了集合的扩展性。无论集合怎么变,只要它有对应的迭代器类,就可以使用如下的模版获取并使用集合中的所有元素。

Iterator iterator = ?.iterator(); // ? 代表集合的名称
while (iterator.hasNext()) { // 如果集合中有剩余元素
    Object curr = iterator.next(); // 获取它
    // 使用它
}

3 各角色之间的关系

3.1 角色

3.1.1 Aggregate ( 集合 )

该角色负责 定义 创建 Iterator 角色 的方法 和 集合应该具备 的方法。本案例中,Aggregate 接口扮演了该角色。

3.1.2 Iterator ( 迭代器 )

该角色负责 定义 按一定顺序逐个遍历元素的 接口。本案例中,Iterator 接口扮演了该角色。

3.1.3 ConcreteAggregate ( 具体的集合 )

该角色负责 实现 Aggregate 角色所定义的 方法,它可以创建 ConcreteIterator 角色。本案例中,MyArray 类扮演了该角色。

3.1.4 ConcreteIterator ( 具体的迭代器 )

该角色负责 实现 Iterator 角色定义的 方法,用于遍历对应的 ConcreteAggregate 角色。本案例中,MyArrayIterator 类扮演了该角色。

3.1.4 Client ( 客户端 )

该角色负责 使用 Aggregate 和 Iterator 角色实现业务逻辑,同时 创建了 ConcreteAggregate 和 ConcreteIterator 角色的对象。本案例中,Client 类扮演了该角色。

3.2 类图

alt text

4 注意事项

  • 集合:
    • 集合应隐藏内部表示:集合应确保通过迭代器访问其元素时,不暴露其内部数据结构。
    • 集合应有一个创建迭代器的方法:集合应提供一个方法(如 iterator())来创建与之对应的迭代器实例。
  • 迭代器:
    • 明确迭代操作:迭代器接口应该明确定义迭代所需的操作,如 hasNext()(是否有下一个元素)、next()(获取下一个元素)等。
    • 迭代器的状态管理:迭代器需要管理其 遍历状态,如 当前位置是否遍历完所有元素 等。
    • 通过迭代器访问元素客户端 应通过迭代器接口来访问集合中的元素,而不是直接访问集合的内部数据结构。
    • 迭代器的生命周期迭代器的生命周期应与其对应的集合相关联。当集合被销毁或发生结构性变化时,相应的迭代器可能变得无效。
    • 迭代器的有效性检查在使用迭代器之前,应检查其是否有效。尝试使用无效的迭代器进行遍历可能会导致运行时错误。
    • 迭代器的效率:迭代器的实现应 尽可能高效,以减少遍历集合时的性能开销。
    • 迭代器的内存使用:在处理大型数据集时,应注意迭代器模式可能引入的额外内存使用,采取相应的优化措施。

5 在源码中的使用

想必大家应该经常使用 JDK 中的 ArrayList 类,这个类就体现了迭代器模式的思想:

  • Aggregate 角色:List<E> 接口扮演该角色,定义了 Iterator<E> iterator(); 的方法:
    Iterator<E> iterator();
    
  • Iterator 角色:Iterator<E> 接口扮演该角色,定义了 boolean hasNext(); E next(); 这两个方法:
    boolean hasNext();
    E next();
    
  • ConcreteAggregate 角色:ArrayList<E> 类扮演该角色,实现了 Iterator<E> iterator(); 方法:
    public Iterator<E> iterator() {
        return new Itr();
    }
    transient Object[] elementData; // ArrayList 底层用于储存数据的数组
    
  • ConcreteIterator 角色:ArrayList<E> 类的内部类 Itr 类扮演了该角色,实现了 boolean hasNext(); E next(); 这两个方法:
    int cursor; // 当前元素的索引
    int lastRet = -1; // 最后一次调用 next() 前 cursor 的值
    public boolean hasNext() {
        return cursor != size;
    }
    
    public E next() {
        checkForComodification(); // 无需关心本方法,本方法与迭代器模式无关
        int i = cursor;
        if (i >= size) // 在没有足够的元素时报错
            throw new NoSuchElementException();
        // 这里的 ArrayList.this.elementData 是外部类 ArrayList 的一个字段
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) // 在索引越界时报错
            throw new ConcurrentModificationException();
        cursor = i + 1; // 指针指向下一个元素
        return (E) elementData[lastRet = i]; // 记录 cursor 移动之前的值
    }
    

说明:在 Itr 类中没有发现其聚合了 ArrayList<E> 类的对象,这是因为 Itr 类是 ArrayList<E> 的内部类,直接就可以使用 ArrayList<E> 类的底层数据结构 Object[] elementData

6 优缺点

优点

  • 支持以不同的方式遍历一个聚合对象:迭代器简化了遍历方式,使得一个 ConcreteAggregate 可以通过不同的 ConcreteIterator 来以不同的方式遍历。
  • 简化了集合的接口:由于引入了迭代器,集合不再需要暴露其内部表示和遍历算法,从而简化了集合的接口。
  • 降低了系统的耦合性:迭代器模式使得遍历算法独立于集合,从而解开集合的 管理(增删改)与 遍历 之间的耦合。
  • 提高了代码的复用性:无论集合的内部实现如何变化,迭代器都提供了统一的遍历接口,使得遍历集合的代码复用性很高。
  • 支持“懒加载”:在某些情况下,迭代器可以只加载集合的部分元素,只有在真正需要元素时才加载它,这有助于节省内存和提高效率。

缺点

  • 增加了类的数量:在实现迭代器模式时,需要为每个 ConcreteAggregate 实现一个对应的 ConcreteIterator,这会增加类的数量,使得系统变得更加复杂。
  • 迭代器与集合的耦合:虽然迭代器模式降低了集合的 管理 与 遍历 之间的耦合,但 迭代器本身 与 集合 之间存在一定的耦合关系,如果集合的内部实现发生较大变化,则可能需要修改迭代器的实现。
  • 性能考虑:如果 集合中元素数量很少,或者 遍历操作不频繁,那么使用迭代器模式可能会降低性能,不如让集合直接暴露用于遍历的接口。

7 适用场景

  • 提供统一接口:当 需要为不同的集合(如 数组、链表 等)提供 统一 的遍历方式 时,迭代器模式非常有用。通过迭代器,客户端代码可以不必了解集合的具体实现,只需通过迭代器接口进行遍历。
  • 支持多种遍历方式:对于一个集合,可以通过不同的迭代器实现 提供多种遍历方式(如 正序遍历、倒序遍历 等),从而满足不同的遍历需求。
  • 支持复杂数据结构的遍历对于 树 和 图 等复杂数据结构,迭代器模式提供了简化的遍历接口。通过实现特定的迭代器类,可以轻松地使用 树 或 图 的 深度优先搜索(DFS)或 广度优先搜索(BFS)。
  • 按需加载:在 处理大型数据集或无限数据流 时,迭代器模式支持 按需加载 数据元素。这意味着迭代器可以逐个处理数据项,而无需一次性将所有数据加载到内存中,从而提高了内存利用率和系统的响应速度。例如在处理大型文件时,可以使用迭代器模式逐行读取文件内容,而不是一次性将整个文件加载到内存中。

8 总结

迭代器模式 是一种 行为型 设计模式,它 使集合能够按照某种顺序被客户端遍历而不需要暴露其内部表示,核心思想是 将集合的遍历行为抽象出来,放入一个独立的迭代器类中

本模式将集合的 管理遍历 分离开来,降低了 耦合性,使得遍历集合的代码具有很高的 复用性,而且可以实现 按需加载 的功能,从而节省内存空间。

但是这种模式会 增加类的数量,进而增加 系统复杂度。此外,本模式可能还会 降低性能,这就要求我们在使用本模式前仔细考虑 集合中的元素数量遍历操作的频度 了。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值