设计模式系列文章(基础篇第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()方法,实现具体迭代器时,重置游标到初始位置,支持迭代器的重复使用,减少对象创建开销。

八、系列文章预告

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

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

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

相关推荐
禅思院3 小时前
前端请求取消与调度完全指南:从 AbortController 到企业级优先级架构
前端·设计模式·前端框架
小bo波3 小时前
用匿名内部类优雅地计算方法执行时间
java·设计模式·性能测试·模板方法模式·lambda·代码优化·匿名内部类
写代码的小阿帆4 小时前
行为型设计模式之观察者(发布-订阅)模式
设计模式
王_teacher5 小时前
23种设计模式全解析(GoF 设计模式)
设计模式·软考·软件设计师·软考中级
阿坤带你走近大数据6 小时前
分别介绍下java主流的开发框架、设计模式与对应编程语言的高级特性
java·开发语言·设计模式
geovindu6 小时前
go: Coroutines Pattern
开发语言·后端·设计模式·golang·协程模式
Anastasiozzzz7 小时前
构建健壮软件系统的基石:深入解析面向对象设计七大原则
开发语言·javascript·设计模式·ecmascript
qq_297574671 天前
设计模式系列文章(基础篇第19篇):中介者模式——封装交互关系,解耦网状依赖
设计模式·交互·中介者模式
AI大法师1 天前
老牌媒体怎么从“出版物更新”走到“品牌系统升级”
大数据·人工智能·设计模式·新媒体运营