迭代器模式:设计与实践
一、什么是迭代器模式
1. 基本定义
迭代器模式(Iterator Pattern)是一种行为型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。
该模式通过定义一个迭代器对象,负责聚合对象的遍历逻辑,使聚合对象与遍历行为分离。核心是将“遍历集合元素”的操作封装在迭代器中,聚合对象只需提供获取迭代器的接口,无需关心具体的遍历实现,从而简化聚合对象的设计,同时支持多种遍历方式。
2. 核心思想
迭代器模式的核心在于分离聚合与遍历。当系统中存在多种聚合对象(如列表、映射、树),且需要对这些对象进行遍历操作时,通过提取遍历逻辑作为迭代器,可避免聚合对象中充斥大量遍历方法,同时使同一聚合对象支持多种遍历方式(如正序、倒序、过滤遍历)。这种设计既保证了聚合对象的简洁性,又为遍历操作提供了灵活性。
二、迭代器模式的特点
1. 遍历与聚合分离
迭代器负责遍历逻辑,聚合对象仅负责数据存储,两者职责单一,降低耦合度。
2. 接口统一
所有迭代器实现相同的接口(如hasNext()、next()),客户端可通过统一接口遍历不同的聚合对象。
3. 隐藏内部结构
客户端无需知道聚合对象的内部存储结构(如数组、链表、哈希表),只需通过迭代器访问元素。
4. 支持多种遍历
同一聚合对象可提供多个迭代器实现,支持正序、倒序、过滤等多种遍历方式。
5. 懒加载能力
迭代器可采用“按需加载”模式,仅在需要时才获取下一个元素,节省内存资源。
| 特点 | 说明 |
|---|---|
| 遍历与聚合分离 | 迭代器负责遍历,聚合对象负责存储,职责分离 |
| 接口统一 | 所有迭代器实现相同接口,遍历方式一致 |
| 隐藏内部结构 | 客户端无需知道聚合对象的存储细节 |
| 支持多种遍历 | 同一聚合可提供多个迭代器,支持不同遍历方式 |
| 懒加载能力 | 可按需加载元素,适合大数据量场景 |
三、迭代器模式的标准代码实现
1. 模式结构
迭代器模式包含四个核心角色:
- 迭代器接口(Iterator):定义遍历元素的方法(如
hasNext()判断是否有下一个元素,next()获取下一个元素)。 - 具体迭代器(ConcreteIterator):实现迭代器接口,包含遍历聚合对象的状态(如当前位置),负责实际的遍历逻辑。
- 聚合接口(Aggregate):定义创建迭代器的方法(如
createIterator()),是聚合对象的抽象表示。 - 具体聚合(ConcreteAggregate):实现聚合接口,存储元素集合,返回具体迭代器实例。
2. 代码实现示例
2.1 迭代器接口
/**
* 迭代器接口
* 定义遍历元素的标准方法
*/
public interface Iterator<T> {
/**
* 判断是否还有下一个元素
*/
boolean hasNext();
/**
* 获取下一个元素
*/
T next();
}
2.2 聚合接口
/**
* 聚合接口
* 定义创建迭代器的方法
*/
public interface Aggregate<T> {
/**
* 创建迭代器
*/
Iterator<T> createIterator();
}
2.3 具体聚合与迭代器
import java.util.ArrayList;
import java.util.List;
/**
* 具体聚合:自定义列表
*/
public class CustomList<T> implements Aggregate<T> {
private List<T> elements = new ArrayList<>();
public void add(T element) {
elements.add(element);
}
public T get(int index) {
return elements.get(index);
}
public int size() {
return elements.size();
}
@Override
public Iterator<T> createIterator() {
// 返回具体迭代器
return new CustomListIterator<>(this);
}
}
/**
* 具体迭代器:自定义列表迭代器
*/
public class CustomListIterator<T> implements Iterator<T> {
private CustomList<T> list;
private int index = 0; // 当前遍历位置
public CustomListIterator(CustomList<T> list) {
this.list = list;
}
@Override
public boolean hasNext() {
// 判断当前位置是否小于列表大小
return index < list.size();
}
@Override
public T next() {
// 获取当前元素并移动到下一个位置
T element = list.get(index);
index++;
return element;
}
}
2.4 客户端使用示例
/**
* 客户端:使用迭代器遍历聚合对象
*/
public class Client {
public static void main(String[] args) {
// 创建聚合对象并添加元素
CustomList<String> list = new CustomList<>();
list.add("元素1");
list.add("元素2");
list.add("元素3");
// 获取迭代器
Iterator<String> iterator = list.createIterator();
// 遍历元素
System.out.println("遍历自定义列表:");
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
}
}
3. 代码实现特点总结
| 角色 | 核心职责 | 代码特点 |
|---|---|---|
| 迭代器接口(Iterator) | 定义遍历元素的标准方法 | 声明hasNext()和next()方法,是所有迭代器的统一接口 |
| 具体迭代器(ConcreteIterator) | 实现遍历逻辑,维护遍历状态 | 持有聚合对象引用,包含index等状态变量,实现hasNext()和next()方法 |
| 聚合接口(Aggregate) | 定义创建迭代器的方法 | 声明createIterator()方法,提供获取迭代器的接口 |
| 具体聚合(ConcreteAggregate) | 存储元素,提供获取迭代器的实现 | 包含元素集合(如List),实现createIterator()方法,返回具体迭代器实例 |
四、支付框架设计中迭代器模式的运用
以账单分页查询的透明遍历为例,说明迭代器模式在支付系统中的具体实现:
1. 场景分析
支付系统中,商户账单可能包含数万条交易记录,直接加载全部数据会导致内存溢出。实际场景中需分页从数据库加载数据,但客户端希望像遍历普通列表一样透明访问,无需关心分页细节。
使用迭代器模式可将分页逻辑封装在迭代器中,客户端通过统一接口遍历账单,迭代器内部自动处理分页加载(如当前页数据用尽时,自动加载下一页),实现“透明遍历”。
2. 设计实现
2.1 数据模型与迭代器接口
import java.math.BigDecimal;
import java.time.LocalDate;
/**
* 账单模型
*/
public class Bill {
private String billId; // 账单ID
private LocalDate month; // 账单月份
private BigDecimal totalAmount; // 总金额
private String merchantId; // 商户ID
// getter和setter方法...
}
/**
* 账单迭代器接口(扩展标准迭代器)
*/
public interface BillIterator extends Iterator<Bill> {
// 支持按商户ID过滤
BillIterator filterByMerchantId(String merchantId);
}
2.2 具体迭代器实现(自动分页)
import java.util.List;
/**
* 账单分页迭代器
* 封装分页查询逻辑,自动加载下一页
*/
public class BillPageIterator implements BillIterator {
private final BillRepository billRepository; // 账单数据访问层
private final int pageSize; // 每页大小
private int currentPage = 0; // 当前页号
private List<Bill> currentPageData; // 当前页数据
private int currentIndex = 0; // 当前页内索引
private String filterMerchantId; // 过滤的商户ID
public BillPageIterator(BillRepository billRepository, int pageSize) {
this.billRepository = billRepository;
this.pageSize = pageSize;
// 预加载第一页数据
loadPageData();
}
/**
* 加载当前页数据
*/
private void loadPageData() {
if (filterMerchantId == null) {
// 无过滤条件,查询全部
currentPageData = billRepository.queryByPage(currentPage, pageSize);
} else {
// 按商户ID过滤查询
currentPageData = billRepository.queryByMerchantAndPage(
filterMerchantId, currentPage, pageSize);
}
currentIndex = 0; // 重置页内索引
}
@Override
public boolean hasNext() {
// 1. 当前页数据已遍历完,尝试加载下一页
while (currentIndex >= currentPageData.size()) {
currentPage++;
loadPageData();
// 若下一页无数据,说明遍历结束
if (currentPageData.isEmpty()) {
return false;
}
}
return true;
}
@Override
public Bill next() {
// 获取当前元素并移动索引
Bill bill = currentPageData.get(currentIndex);
currentIndex++;
return bill;
}
@Override
public BillIterator filterByMerchantId(String merchantId) {
this.filterMerchantId = merchantId;
// 重置分页状态,从第0页开始查询
currentPage = 0;
loadPageData();
return this;
}
}
2.3 聚合接口与具体聚合
/**
* 账单聚合接口
*/
public interface BillAggregate {
BillIterator createIterator(int pageSize);
}
/**
* 具体聚合:商户账单集合
*/
public class MerchantBillAggregate implements BillAggregate {
private final BillRepository billRepository;
public MerchantBillAggregate(BillRepository billRepository) {
this.billRepository = billRepository;
}
@Override
public BillIterator createIterator(int pageSize) {
// 返回分页迭代器
return new BillPageIterator(billRepository, pageSize);
}
}
2.4 数据访问层与客户端使用
import java.util.List;
/**
* 账单数据访问层(模拟)
*/
public class BillRepository {
/**
* 按页查询账单
*/
public List<Bill> queryByPage(int pageNum, int pageSize) {
// 实际中执行数据库分页查询(如SELECT * FROM bill LIMIT pageSize OFFSET pageNum*pageSize)
System.out.println("查询第" + pageNum + "页,每页" + pageSize + "条账单");
return mockBillData(pageNum, pageSize);
}
/**
* 按商户ID和页查询账单
*/
public List<Bill> queryByMerchantAndPage(String merchantId, int pageNum, int pageSize) {
System.out.println("查询商户" + merchantId + "第" + pageNum + "页账单");
return mockBillData(pageNum, pageSize);
}
// 模拟账单数据
private List<Bill> mockBillData(int pageNum, int pageSize) {
// 实际中从数据库获取,此处简化为模拟数据
return List.of(new Bill());
}
}
/**
* 客户端:账单服务(生成结算单)
*/
public class BillStatementService {
private final BillAggregate billAggregate;
public BillStatementService(BillAggregate billAggregate) {
this.billAggregate = billAggregate;
}
/**
* 生成商户的完整结算单
*/
public void generateFullStatement(String merchantId) {
// 获取迭代器(每页100条)
BillIterator iterator = billAggregate.createIterator(100)
.filterByMerchantId(merchantId);
// 遍历账单,生成结算单
while (iterator.hasNext()) {
Bill bill = iterator.next();
System.out.println("生成账单结算单:" + bill.getBillId() +
",月份:" + bill.getMonth() +
",金额:" + bill.getTotalAmount());
}
}
public static void main(String[] args) {
// 初始化依赖
BillRepository repository = new BillRepository();
BillAggregate aggregate = new MerchantBillAggregate(repository);
BillStatementService service = new BillStatementService(aggregate);
// 生成商户"MCH001"的结算单
service.generateFullStatement("MCH001");
}
}
3. 模式价值体现
- 透明遍历:客户端无需手动处理分页参数(
currentPage、pageSize),迭代器自动加载下一页,遍历逻辑与普通列表一致 - 按需加载:采用“懒加载”模式,只有当前页数据用尽时才加载下一页,避免一次性加载数万条账单导致的内存溢出
- 职责分离:账单存储(
BillRepository)与遍历逻辑(BillPageIterator)分离,聚合对象专注于数据管理,迭代器专注于遍历 - 灵活过滤:迭代器支持按商户ID等条件过滤,遍历过程中自动应用过滤条件,客户端无需先筛选再遍历
- 多数据源适配:若账单数据从数据库迁移到Redis,只需修改迭代器的
loadPageData方法,客户端代码无需变动
五、开源框架中迭代器模式的运用
以Java集合框架(JDK Collections) 为例,说明迭代器模式在开源框架中的典型应用:
1. 核心实现分析
Java集合框架(如ArrayList、HashMap)广泛使用迭代器模式,通过java.util.Iterator接口统一遍历各种集合,隐藏不同集合的内部结构(数组、链表、哈希表)。
1.1 迭代器接口(JDK定义)
public interface Iterator<E> {
boolean hasNext();
E next();
// 可选的删除方法
default void remove() {
throw new UnsupportedOperationException("remove");
}
}
1.2 具体聚合与迭代器(以ArrayList为例)
ArrayList作为具体聚合,实现Iterable接口(类似Aggregate),提供iterator()方法返回迭代器:
public class ArrayList<E> extends AbstractList<E> implements List<E> {
private transient Object[] elementData; // 内部用数组存储元素
@Override
public Iterator<E> iterator() {
return new Itr(); // 返回内部迭代器
}
// 内部迭代器实现
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的索引
int lastRet = -1; // 最后返回元素的索引
@Override
public boolean hasNext() {
return cursor != size;
}
@Override
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
}
1.3 客户端使用
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CollectionClient {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("元素1");
list.add("元素2");
// 获取迭代器并遍历
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
}
}
2. 迭代器模式在Java集合中的价值
- 接口统一:无论遍历
ArrayList、LinkedList还是HashMap,都通过Iterator接口,客户端代码无需适配不同集合类型 - 隐藏实现:
ArrayList用数组存储,LinkedList用链表存储,但迭代器接口屏蔽了这些差异 - 支持多种遍历:集合框架提供
Iterator(普通遍历)、ListIterator(列表双向遍历)等,满足不同场景需求 - 快速失败机制:迭代器实现
checkForComodification方法,检测集合在遍历过程中是否被修改,及时抛出ConcurrentModificationException,避免数据不一致
六、总结
1. 迭代器模式的适用场景
- 当需要遍历聚合对象,且不希望暴露其内部存储结构时
- 当同一聚合对象需要支持多种遍历方式(如正序、倒序、过滤)时
- 当聚合对象的内部结构可能变化(如从数组改为链表),但希望遍历接口保持稳定时
- 当处理大数据量集合(如分页查询的账单、交易流水),需要按需加载元素时
2. 迭代器模式与其他模式的区别
- 与访问者模式:两者都涉及遍历集合,但访问者模式专注于对元素执行不同操作,迭代器模式专注于遍历本身,前者是“操作扩展”,后者是“遍历扩展”
- 与组合模式:组合模式用于处理树形结构,迭代器模式可用于遍历组合模式中的节点集合,前者是“结构设计”,后者是“遍历工具”
- 与工厂模式:工厂模式用于创建对象,迭代器模式中的
createIterator方法可视为“迭代器工厂”,但迭代器模式的核心是遍历,而非对象创建
3. 支付系统中的实践价值
- 简化客户端逻辑:客户端无需处理分页、过滤等复杂逻辑,通过统一接口遍历,专注于业务处理(如生成结算单)
- 内存友好:支持按需加载大数据量集合(如超长账单、交易流水),避免内存溢出
- 适配多数据源:账单数据从数据库迁移到Redis时,只需修改迭代器实现,客户端代码不变
- 便于扩展:新增遍历方式(如按金额排序遍历)只需添加新的迭代器,无需修改聚合对象和客户端
4. 实践建议
- 优先使用标准接口:尽量实现
java.util.Iterator接口,遵循开发规范,降低学习成本 - 迭代器不可变:避免在遍历过程中修改聚合对象(如添加/删除元素),否则可能导致迭代异常
- 支持关闭资源:对于需要释放的资源(如数据库连接),迭代器可实现
AutoCloseable接口,确保资源正确释放 - 按需实现过滤功能:在支付场景中,迭代器可扩展
filterByXXX方法,支持按商户、金额等条件过滤,增强实用性
迭代器模式通过“分离聚合与遍历”的思想,为支付系统中复杂集合的遍历提供了优雅解决方案,既保证了聚合对象的封装性,又为遍历操作提供了灵活性,是处理大数据量集合场景的必备设计模式。