设计模式系列文章(基础篇第21篇):迭代器模式——遍历聚合解耦,实现统一迭代访问

大家好,欢迎来到设计模式系列文章(基础篇)的第二十一篇内容。在上一篇中,我们学习了行为型模式的第十种常用模式------观察者模式,其核心是定义对象间的一对多依赖关系,实现发布者与订阅者的解耦,当发布者状态变化时,所有订阅者会自动收到通知并更新,广泛应用于消息通知、数据同步、事件监听等场景,也是框架底层事件驱动的核心思想。今天,我们将学习行为型模式的第十一种常用模式------迭代器模式,它的核心是提供一种统一的方法,顺序访问一个聚合对象(如集合、数组)中的所有元素,而无需暴露该聚合对象的内部表示(如底层存储结构、数据类型),将元素的遍历逻辑与聚合对象本身解耦,让遍历逻辑可以独立扩展,广泛应用于集合遍历、数据迭代等场景,更是Java集合框架(如List、Set、Map)的核心设计思想之一。

在日常开发中,我们经常需要遍历各种聚合对象:比如遍历一个List集合中的所有元素、遍历一个数组中的数据、遍历一个自定义的商品列表。如果不使用迭代器模式,我们需要在聚合对象内部编写遍历逻辑,或者在客户端中直接操作聚合对象的内部结构------比如直接访问List的下标、数组的索引,这种方式会导致客户端与聚合对象强耦合:客户端必须了解聚合对象的内部存储结构(是数组、链表还是其他),才能完成遍历;如果聚合对象的内部结构发生变化(比如从数组改为链表),客户端的遍历代码也必须随之修改,违背开闭原则;同时,不同聚合对象的遍历逻辑分散在各个聚合类中,代码冗余且难以维护。

而迭代器模式,通过引入一个迭代器对象,将遍历逻辑从聚合对象中分离出来,聚合对象只负责存储数据,迭代器负责提供统一的遍历方法(如获取下一个元素、判断是否还有元素)。客户端只需通过迭代器访问聚合对象的元素,无需关心聚合对象的内部结构,也无需编写具体的遍历逻辑;新增聚合对象或修改遍历逻辑时,只需新增对应的迭代器,无需改动客户端和其他聚合对象,完美实现遍历与聚合的解耦。今天,我们就从核心定义、结构、实战实现、场景对比、避坑指南全维度讲解,帮大家彻底掌握这种"统一遍历、解耦聚合"的实用设计模式。

一、迭代器模式的核心定义与设计初衷

1. 核心定义

迭代器模式(Iterator Pattern):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式又称游标模式(Cursor Pattern),核心是分离聚合对象与遍历逻辑,提供统一的迭代访问接口。

通俗理解:迭代器模式就像我们生活中的图书管理员。图书馆(聚合对象)负责存储图书(元素),图书管理员(迭代器)负责带领读者(客户端)顺序浏览图书馆中的所有图书,读者无需知道图书馆的图书是如何分类、如何存储的(内部结构),只需跟随图书管理员,就能依次看到每一本图书。图书管理员提供了统一的"浏览"方法(比如"下一本""是否还有图书"),无论图书馆的存储方式如何变化(比如从书架改为密集架),只要图书管理员的浏览方法不变,读者就无需改变自己的浏览方式,实现了读者与图书馆的解耦。

2. 设计初衷(解决的核心问题)

迭代器模式的出现,核心是解决"聚合对象与遍历逻辑耦合、遍历方式不统一"的痛点,具体解决3个核心问题:

  • 解耦聚合与遍历:将遍历逻辑从聚合对象中分离出来,聚合对象只负责存储数据,迭代器负责遍历,客户端无需关心聚合对象的内部结构;
  • 提供统一遍历接口:定义统一的迭代器接口,无论聚合对象的底层存储结构如何(数组、链表、集合),客户端都能通过相同的方法遍历元素,提升代码一致性;
  • 支持多种遍历方式:针对同一个聚合对象,可以提供不同的迭代器(如正向遍历、反向遍历),新增遍历方式时无需修改聚合对象,符合开闭原则。

3. 设计原则适配

迭代器模式作为经典行为型模式,严格贴合面向对象设计核心原则,是实现聚合与遍历解耦的最佳实践:

  • 单一职责原则:聚合对象只负责存储数据,迭代器只负责遍历数据,职责单一,便于维护和扩展;
  • 开闭原则:新增聚合对象时,只需实现对应的迭代器;新增遍历方式时,只需新增迭代器类,无需修改客户端和现有聚合对象;
  • 迪米特法则(最少知道原则):客户端只需知道迭代器接口,无需知道聚合对象的内部结构和迭代器的具体实现,减少对象间的交互细节;
  • 依赖倒转原则:客户端依赖迭代器抽象接口,不依赖具体迭代器实现;聚合对象依赖迭代器抽象接口,便于替换不同的迭代器。

二、迭代器模式的核心结构(4个核心角色)

迭代器模式的结构清晰,核心包含4个角色,各司其职、协同完成聚合对象的遍历,我们以"自定义商品列表"为场景,逐一拆解角色职责与交互逻辑:

1. 抽象迭代器(Iterator)

迭代器模式的核心接口,定义了统一的遍历方法,用于访问聚合对象中的元素,通常包含以下4个核心方法:

  • hasNext():判断聚合对象中是否还有下一个元素,返回布尔值;
  • next():获取聚合对象中的下一个元素;
  • remove()(可选):删除当前遍历到的元素;
  • reset()(可选):重置迭代器,回到遍历的起始位置。
    抽象迭代器屏蔽了具体迭代器的实现细节,让客户端只需依赖抽象接口,就能完成遍历操作。对应商品列表场景中的"商品迭代器接口",定义遍历商品的通用方法。

2. 具体迭代器(Concrete Iterator)

实现抽象迭代器接口,持有聚合对象的引用,维护当前遍历的位置(游标),实现具体的遍历逻辑。具体迭代器负责跟踪遍历进度,根据聚合对象的内部结构,实现hasNext()、next()等方法,完成元素的顺序访问。对应商品列表场景中的"商品具体迭代器",根据商品列表的存储结构(如数组),实现商品的遍历。

3. 抽象聚合(Aggregate)

所有具体聚合对象的抽象父类或接口,定义了创建对应迭代器对象的方法(如createIterator()),同时提供维护聚合元素的通用方法(如添加元素、删除元素)。抽象聚合屏蔽了具体聚合的实现细节,让客户端只需依赖抽象聚合,就能创建迭代器并遍历元素。对应商品列表场景中的"商品聚合接口",定义创建商品迭代器、管理商品的方法。

4. 具体聚合(Concrete Aggregate)

实现抽象聚合接口,维护聚合对象的内部数据(如数组、链表),实现创建具体迭代器的方法------返回与自身存储结构匹配的具体迭代器对象。具体聚合是元素的存储载体,负责存储数据,不负责遍历逻辑,遍历逻辑委托给具体迭代器。对应商品列表场景中的"具体商品列表",存储商品数据,创建商品迭代器。

核心关系总结:抽象聚合定义聚合规范,具体聚合实现数据存储并创建具体迭代器;抽象迭代器定义遍历规范,具体迭代器实现遍历逻辑并访问具体聚合的元素。客户端通过抽象聚合创建抽象迭代器,再通过抽象迭代器遍历具体聚合中的元素,全程无需关心具体聚合和具体迭代器的实现,实现了解耦。

三、迭代器模式的核心逻辑与执行流程

迭代器模式的核心逻辑是"分离聚合与遍历、统一迭代接口",标准执行流程分为五步,全程无客户端与聚合对象直接耦合,逻辑闭环:

  1. 定义抽象迭代器接口:声明hasNext()、next()等统一的遍历方法;
  2. 定义抽象聚合接口:声明创建迭代器的方法和维护元素的通用方法;
  3. 实现具体聚合类:维护内部数据结构,实现创建具体迭代器的方法;
  4. 实现具体迭代器类:持有具体聚合的引用,维护遍历游标,实现抽象迭代器的所有方法;
  5. 客户端初始化:创建具体聚合对象,添加元素,通过具体聚合创建具体迭代器,使用迭代器的遍历方法,顺序访问聚合中的所有元素。
    关键要点:具体聚合只负责存储数据,不包含任何遍历逻辑;具体迭代器依赖具体聚合的内部结构,但客户端无需关心这种依赖;客户端通过统一的迭代器接口遍历元素,无论聚合对象的内部结构如何变化,客户端代码无需修改。

四、迭代器模式的实战实现(自定义商品列表场景)

我们以高频的自定义商品列表为场景,使用Java代码实现迭代器模式,模拟商品列表(具体聚合)存储商品数据,通过商品迭代器(具体迭代器)顺序遍历商品,客户端无需关心商品列表的内部存储结构(数组),只需通过迭代器即可访问所有商品,直观体现迭代器模式解耦聚合与遍历、统一迭代接口的核心优势。

场景说明:自定义商品列表(具体聚合)使用数组存储商品数据,支持添加商品、删除商品;商品迭代器(具体迭代器)实现正向遍历商品的功能,支持判断是否有下一个商品、获取下一个商品;客户端通过迭代器遍历商品列表,无需直接操作数组。

1. 第一步:定义商品实体类(聚合元素)

bash 复制代码
// 商品实体类(聚合对象中的元素)
public class Product {
    // 商品ID
    private String productId;
    // 商品名称
    private String productName;
    // 商品价格
    private double price;

    // 构造方法
    public Product(String productId, String productName, double price) {
        this.productId = productId;
        this.productName = productName;
        this.price = price;
    }

    // getter方法(便于迭代器获取商品信息)
    public String getProductId() {
        return productId;
    }

    public String getProductName() {
        return productName;
    }

    public double getPrice() {
        return price;
    }

    // 重写toString方法,便于打印商品信息
    @Override
    public String toString() {
        return "Product{" +
                "productId='" + productId + '\'' +
                ", productName='" + productName + '\'' +
                ", price=" + price +
                '}';
    }
}

2. 第二步:定义抽象迭代器接口(Iterator)

bash 复制代码
// 抽象迭代器:商品迭代器接口
public interface Iterator {
    // 判断是否还有下一个商品
    boolean hasNext();
    // 获取下一个商品
    Product next();
    // 可选:删除当前遍历到的商品
    void remove();
}

3. 第三步:定义抽象聚合接口(Aggregate)

bash 复制代码
// 抽象聚合:商品聚合接口
public interface Aggregate {
    // 创建迭代器对象
    Iterator createIterator();
    // 添加商品
    void addProduct(Product product);
    // 删除商品
    void removeProduct(Product product);
    // 获取商品数量
    int getSize();
}

4. 第四步:实现具体聚合类(ProductList)

bash 复制代码
// 具体聚合:自定义商品列表(使用数组存储商品)
public class ProductList implements Aggregate {
    // 内部存储结构:数组
    private Product[] products;
    // 商品数量(当前实际存储的商品个数)
    private int size = 0;
    // 数组初始容量
    private static final int INIT_CAPACITY = 10;

    // 构造方法:初始化数组
    public ProductList() {
        products = new Product[INIT_CAPACITY];
    }

    @Override
    public Iterator createIterator() {
        // 返回与当前聚合匹配的具体迭代器
        return new ProductIterator(this);
    }

    @Override
    public void addProduct(Product product) {
        // 数组扩容(简单实现)
        if (size >= products.length) {
            Product[] newProducts = new Product[products.length * 2];
            System.arraycopy(products, 0, newProducts, 0, products.length);
            products = newProducts;
        }
        products[size++] = product;
    }

    @Override
    public void removeProduct(Product product) {
        // 遍历数组,删除指定商品
        for (int i = 0; i < size; i++) {
            if (products[i].equals(product)) {
                // 移动数组元素,覆盖被删除的商品
                System.arraycopy(products, i + 1, products, i, size - i - 1);
                size--;
                break;
            }
        }
    }

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

    // 提供方法,供迭代器访问内部数组(仅暴露必要的访问方式,不暴露完整数组)
    public Product getProduct(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("索引越界");
        }
        return products[index];
    }
}

5. 第五步:实现具体迭代器类(ProductIterator)

bash 复制代码
// 具体迭代器:商品迭代器(正向遍历)
public class ProductIterator implements Iterator {
    // 持有具体聚合的引用,用于访问聚合中的元素
    private ProductList productList;
    // 遍历游标:记录当前遍历到的索引位置
    private int index = 0;

    // 构造方法:传入具体聚合对象
    public ProductIterator(ProductList productList) {
        this.productList = productList;
    }

    @Override
    public boolean hasNext() {
        // 判断游标是否小于商品数量,若小于则还有下一个元素
        return index < productList.getSize();
    }

    @Override
    public Product next() {
        // 获取当前游标位置的商品,并将游标向后移动一位
        if (hasNext()) {
            return productList.getProduct(index++);
        }
        throw new NoSuchElementException("没有更多商品了");
    }

    @Override
    public void remove() {
        // 删除当前遍历到的商品(游标需回退一位,避免跳过下一个元素)
        if (index > 0) {
            productList.removeProduct(productList.getProduct(--index));
        } else {
            throw new IllegalStateException("无法删除未遍历的元素");
        }
    }
}

6. 第六步:客户端测试

bash 复制代码
// 客户端测试:自定义商品列表遍历场景
public class IteratorPatternTest {
    public static void main(String[] args) {
        // 1. 创建具体聚合对象(商品列表)
        Aggregate productList = new ProductList();

        // 2. 向商品列表添加商品
        productList.addProduct(new Product("P001", "Java编程思想", 89.9));
        productList.addProduct(new Product("P002", "设计模式详解", 69.9));
        productList.addProduct(new Product("P003", "SpringBoot实战", 79.9));
        productList.addProduct(new Product("P004", "MySQL从入门到精通", 59.9));

        // 3. 创建迭代器对象
        Iterator iterator = productList.createIterator();

        // 4. 使用迭代器遍历商品列表
        System.out.println("=== 正向遍历所有商品 ===");
        while (iterator.hasNext()) {
            Product product = iterator.next();
            System.out.println(product);
        }

        // 5. 重置迭代器,测试删除功能
        iterator = productList.createIterator(); // 重新创建迭代器,重置游标
        System.out.println("\n=== 遍历并删除指定商品 ===");
        while (iterator.hasNext()) {
            Product product = iterator.next();
            if ("P002".equals(product.getProductId())) {
                iterator.remove();
                System.out.println("已删除商品:" + product);
                break;
            }
        }

        // 6. 再次遍历,查看删除后的结果
        System.out.println("\n=== 删除后的商品列表 ===");
        iterator = productList.createIterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

运行结果

bash 复制代码
=== 正向遍历所有商品 ===
Product{productId='P001', productName='Java编程思想', price=89.9}
Product{productId='P002', productName='设计模式详解', price=69.9}
Product{productId='P003', productName='SpringBoot实战', price=79.9}
Product{productId='P004', productName='MySQL从入门到精通', price=59.9}

=== 遍历并删除指定商品 ===
已删除商品:Product{productId='P002', productName='设计模式详解', price=69.9}

=== 删除后的商品列表 ===
Product{productId='P001', productName='Java编程思想', price=89.9}
Product{productId='P003', productName='SpringBoot实战', price=79.9}
Product{productId='P004', productName='MySQL从入门到精通', price=59.9}

从运行结果可以看出,客户端通过迭代器接口,无需关心商品列表的内部存储结构(数组),就能完成商品的遍历和删除操作;当商品列表的内部存储结构发生变化(比如从数组改为链表)时,只需修改具体聚合和具体迭代器的实现,客户端代码无需任何改动,完美体现了迭代器模式解耦聚合与遍历的核心优势。

五、迭代器模式的高频应用场景

迭代器模式适用于所有需要遍历聚合对象、且需要解耦聚合与遍历逻辑的业务场景,核心作用是统一遍历接口、分离遍历逻辑,以下是四大高频落地场景:

1. 集合框架遍历(最经典应用)

主流编程语言的集合框架,几乎都使用了迭代器模式,提供统一的遍历接口,屏蔽不同集合的内部存储差异:

  • Java集合框架:List、Set、Map等集合,都提供了Iterator迭代器,客户端通过Iterator的hasNext()、next()方法,就能统一遍历不同集合的元素,无需关心集合的内部结构(ArrayList是数组、LinkedList是链表);
  • Python集合:list、tuple、dict等集合,通过for-in循环遍历,底层本质是迭代器模式,隐藏了遍历逻辑;
  • C#集合:ICollection接口提供GetEnumerator()方法,返回迭代器,实现统一遍历。

2. 自定义聚合对象遍历

开发中自定义的聚合对象(如商品列表、用户列表、订单列表),需要提供遍历功能时,使用迭代器模式分离遍历逻辑,提升代码可维护性:

  • 自定义数据结构:如自定义链表、栈、队列等,通过迭代器提供统一的遍历方法;
  • 业务聚合对象:如电商系统的商品列表、订单列表,后台管理系统的用户列表,通过迭代器实现灵活遍历,支持正向、反向等多种遍历方式。

3. 多遍历方式场景

同一个聚合对象需要支持多种遍历方式(如正向遍历、反向遍历、按条件遍历)时,使用迭代器模式,每种遍历方式对应一个具体迭代器,互不干扰:

  • 列表遍历:支持正向遍历、反向遍历,分别对应正向迭代器、反向迭代器;
  • 数据筛选遍历:支持按条件筛选遍历(如遍历价格大于100的商品),对应筛选迭代器;
  • 分页遍历:支持分页遍历聚合对象中的元素,对应分页迭代器。

4. 框架底层与工具类设计

主流开源框架和工具类中,大量使用迭代器模式实现数据遍历与解耦,是框架灵活性的重要保障:

  • MyBatis:查询结果集(ResultSet)的遍历,底层使用迭代器模式,提供统一的遍历接口,屏蔽数据库查询结果的底层实现;
  • Spring:Spring Data JPA的分页查询结果,通过PageIterator实现分页遍历,解耦分页逻辑与数据存储;
  • Apache Commons Collections:提供了多种迭代器实现(如过滤迭代器、反转迭代器),扩展了集合的遍历功能。

六、迭代器模式 vs 其他相似模式(重点区分)

迭代器模式常与组合模式、工厂方法模式混淆,三者都涉及对象的访问与创建,但核心目标、使用场景完全不同,通过表格清晰对比,帮大家快速区分:

对比维度 迭代器模式 组合模式 工厂方法模式
核心目标 分离聚合与遍历逻辑,提供统一的迭代访问接口 将对象组合成树形结构,统一访问单个对象和组合对象 定义创建对象的接口,让子类决定实例化哪个类
核心逻辑 通过迭代器遍历聚合对象的元素,不暴露内部结构 将对象组合成树形结构,递归访问所有子对象 通过工厂方法创建对象,解耦对象创建与使用
适用场景 聚合对象遍历、统一遍历接口(关注"遍历") 树形结构对象访问、整体与部分统一处理(关注"组合") 对象创建、解耦创建与使用(关注"创建")
核心角色 抽象迭代器、具体迭代器、抽象聚合、具体聚合 抽象组件、具体组件、抽象组合、具体组合 抽象工厂、具体工厂、抽象产品、具体产品

一句话总结:迭代器模式管"遍历",组合模式管"组合",工厂方法模式管"创建";需要遍历聚合对象用迭代器模式,需要组合树形结构对象用组合模式,需要解耦对象创建用工厂方法模式,三者可结合使用(如组合模式+迭代器模式,遍历树形结构中的所有元素)。

七、迭代器模式的常见坑与避坑指南

坑1:迭代器遍历过程中,直接修改聚合对象,导致遍历异常

在使用迭代器遍历聚合对象时,如果直接通过聚合对象的方法添加、删除元素(而非迭代器的remove()方法),会导致迭代器的游标与聚合对象的实际状态不一致,引发ConcurrentModificationException(并发修改异常)。

避坑指南:遍历过程中,若需修改聚合对象(添加、删除元素),必须使用迭代器自身的remove()方法(或对应修改方法);若需批量修改,可先记录需要修改的元素,遍历结束后再统一修改。

坑2:过度设计,简单聚合无需使用迭代器模式

部分开发者在简单聚合场景中(如仅遍历一个简单数组、单个集合),强行使用迭代器模式,新增抽象迭代器、具体迭代器、抽象聚合等角色,导致代码冗余、逻辑复杂化,违背设计模式初衷。

避坑指南:只有当聚合对象需要被多次遍历、需要支持多种遍历方式、或需要解耦聚合与遍历逻辑时,才使用迭代器模式;简单聚合场景(如单次遍历、内部结构固定),直接使用普通循环遍历即可,无需过度设计。

坑3:迭代器与聚合对象强耦合,难以扩展

部分开发者在实现时,让具体迭代器直接依赖具体聚合的内部细节(如直接操作聚合的私有数组),导致聚合对象的内部结构变化时,迭代器也必须随之修改,耦合过高。

避坑指南:具体迭代器通过抽象聚合接口访问聚合对象的元素,不直接操作聚合的私有成员;聚合对象提供统一的元素访问方法(如getProduct(int index)),供迭代器调用,降低耦合。

坑4:忽略迭代器的边界判断,导致异常

部分开发者在实现迭代器的next()方法时,未判断边界(即没有元素时仍调用next()),导致NoSuchElementException异常;或hasNext()方法实现错误,导致遍历遗漏或重复元素。

避坑指南:严格实现hasNext()方法,确保准确判断是否还有下一个元素;在next()方法中,先调用hasNext()判断,若没有元素则抛出明确的异常,避免边界错误。

坑5:未提供重置迭代器的方法,导致无法重复遍历

部分迭代器实现中,未提供reset()方法,遍历结束后游标无法重置,若需再次遍历聚合对象,只能重新创建迭代器,增加系统开销。

避坑指南:在抽象迭代器中添加reset()方法,实现具体迭代器时,重置游标到初始位置,支持迭代器的重复使用,减少对象创建开销。

八、系列文章预告

本篇文章,我们详细讲解了迭代器模式的核心定义、四大核心角色、标准执行流程、自定义商品列表场景实战代码、高频应用场景和避坑指南,同时区分了易混淆的组合模式、工厂方法模式。迭代器模式凭借"分离聚合与遍历、统一迭代接口"的优势,成为集合遍历、自定义聚合对象遍历场景的首选设计模式,也是主流编程语言集合框架的核心设计思想,能大幅提升代码的一致性与可维护性。

下一篇,我们将学习行为型模式的第十二种常用模式------访问者模式,它的核心是定义一个访问者对象,封装对聚合对象中元素的操作,让操作可以独立于元素的类而变化,将数据结构与数据操作解耦,广泛应用于数据结构固定、操作多变的场景,如报表生成、数据统计、元素解析等。

访问者模式------分离数据结构与操作,实现灵活扩展。我们不见不散!

相关推荐
杨充1 分钟前
2.面向对象的特性
设计模式
杨充1 分钟前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 分钟前
8.反模式与坏味道
设计模式·开源·代码规范
杨充11 分钟前
3.接口vs抽象类比较
设计模式
咖啡八杯3 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯12 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式
大数据新鸟15 小时前
设计模式四大基本原则
设计模式
南一Nanyi20 小时前
依赖注入和控制反转
前端·设计模式·nestjs
tiankong12131 天前
如何拓展多态下的子类
设计模式·c#