大家好,欢迎来到设计模式系列文章(基础篇)的第二十一篇内容。在上一篇中,我们学习了行为型模式的第十种常用模式------观察者模式,其核心是定义对象间的一对多依赖关系,实现发布者与订阅者的解耦,当发布者状态变化时,所有订阅者会自动收到通知并更新,广泛应用于消息通知、数据同步、事件监听等场景,也是框架底层事件驱动的核心思想。今天,我们将学习行为型模式的第十一种常用模式------迭代器模式,它的核心是提供一种统一的方法,顺序访问一个聚合对象(如集合、数组)中的所有元素,而无需暴露该聚合对象的内部表示(如底层存储结构、数据类型),将元素的遍历逻辑与聚合对象本身解耦,让遍历逻辑可以独立扩展,广泛应用于集合遍历、数据迭代等场景,更是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)
实现抽象聚合接口,维护聚合对象的内部数据(如数组、链表),实现创建具体迭代器的方法------返回与自身存储结构匹配的具体迭代器对象。具体聚合是元素的存储载体,负责存储数据,不负责遍历逻辑,遍历逻辑委托给具体迭代器。对应商品列表场景中的"具体商品列表",存储商品数据,创建商品迭代器。
核心关系总结:抽象聚合定义聚合规范,具体聚合实现数据存储并创建具体迭代器;抽象迭代器定义遍历规范,具体迭代器实现遍历逻辑并访问具体聚合的元素。客户端通过抽象聚合创建抽象迭代器,再通过抽象迭代器遍历具体聚合中的元素,全程无需关心具体聚合和具体迭代器的实现,实现了解耦。
三、迭代器模式的核心逻辑与执行流程
迭代器模式的核心逻辑是"分离聚合与遍历、统一迭代接口",标准执行流程分为五步,全程无客户端与聚合对象直接耦合,逻辑闭环:
- 定义抽象迭代器接口:声明hasNext()、next()等统一的遍历方法;
- 定义抽象聚合接口:声明创建迭代器的方法和维护元素的通用方法;
- 实现具体聚合类:维护内部数据结构,实现创建具体迭代器的方法;
- 实现具体迭代器类:持有具体聚合的引用,维护遍历游标,实现抽象迭代器的所有方法;
- 客户端初始化:创建具体聚合对象,添加元素,通过具体聚合创建具体迭代器,使用迭代器的遍历方法,顺序访问聚合中的所有元素。
关键要点:具体聚合只负责存储数据,不包含任何遍历逻辑;具体迭代器依赖具体聚合的内部结构,但客户端无需关心这种依赖;客户端通过统一的迭代器接口遍历元素,无论聚合对象的内部结构如何变化,客户端代码无需修改。
四、迭代器模式的实战实现(自定义商品列表场景)
我们以高频的自定义商品列表为场景,使用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()方法,实现具体迭代器时,重置游标到初始位置,支持迭代器的重复使用,减少对象创建开销。
八、系列文章预告
本篇文章,我们详细讲解了迭代器模式的核心定义、四大核心角色、标准执行流程、自定义商品列表场景实战代码、高频应用场景和避坑指南,同时区分了易混淆的组合模式、工厂方法模式。迭代器模式凭借"分离聚合与遍历、统一迭代接口"的优势,成为集合遍历、自定义聚合对象遍历场景的首选设计模式,也是主流编程语言集合框架的核心设计思想,能大幅提升代码的一致性与可维护性。
下一篇,我们将学习行为型模式的第十二种常用模式------访问者模式,它的核心是定义一个访问者对象,封装对聚合对象中元素的操作,让操作可以独立于元素的类而变化,将数据结构与数据操作解耦,广泛应用于数据结构固定、操作多变的场景,如报表生成、数据统计、元素解析等。
访问者模式------分离数据结构与操作,实现灵活扩展。我们不见不散!