迭代器模式:设计与实践

19 阅读12分钟

迭代器模式:设计与实践

一、什么是迭代器模式

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. 模式价值体现

  • 透明遍历:客户端无需手动处理分页参数(currentPagepageSize),迭代器自动加载下一页,遍历逻辑与普通列表一致
  • 按需加载:采用“懒加载”模式,只有当前页数据用尽时才加载下一页,避免一次性加载数万条账单导致的内存溢出
  • 职责分离:账单存储(BillRepository)与遍历逻辑(BillPageIterator)分离,聚合对象专注于数据管理,迭代器专注于遍历
  • 灵活过滤:迭代器支持按商户ID等条件过滤,遍历过程中自动应用过滤条件,客户端无需先筛选再遍历
  • 多数据源适配:若账单数据从数据库迁移到Redis,只需修改迭代器的loadPageData方法,客户端代码无需变动

五、开源框架中迭代器模式的运用

Java集合框架(JDK Collections) 为例,说明迭代器模式在开源框架中的典型应用:

1. 核心实现分析

Java集合框架(如ArrayListHashMap)广泛使用迭代器模式,通过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集合中的价值

  • 接口统一:无论遍历ArrayListLinkedList还是HashMap,都通过Iterator接口,客户端代码无需适配不同集合类型
  • 隐藏实现ArrayList用数组存储,LinkedList用链表存储,但迭代器接口屏蔽了这些差异
  • 支持多种遍历:集合框架提供Iterator(普通遍历)、ListIterator(列表双向遍历)等,满足不同场景需求
  • 快速失败机制:迭代器实现checkForComodification方法,检测集合在遍历过程中是否被修改,及时抛出ConcurrentModificationException,避免数据不一致

六、总结

1. 迭代器模式的适用场景

  • 当需要遍历聚合对象,且不希望暴露其内部存储结构时
  • 当同一聚合对象需要支持多种遍历方式(如正序、倒序、过滤)时
  • 当聚合对象的内部结构可能变化(如从数组改为链表),但希望遍历接口保持稳定时
  • 当处理大数据量集合(如分页查询的账单、交易流水),需要按需加载元素时

2. 迭代器模式与其他模式的区别

  • 与访问者模式:两者都涉及遍历集合,但访问者模式专注于对元素执行不同操作,迭代器模式专注于遍历本身,前者是“操作扩展”,后者是“遍历扩展”
  • 与组合模式:组合模式用于处理树形结构,迭代器模式可用于遍历组合模式中的节点集合,前者是“结构设计”,后者是“遍历工具”
  • 与工厂模式:工厂模式用于创建对象,迭代器模式中的createIterator方法可视为“迭代器工厂”,但迭代器模式的核心是遍历,而非对象创建

3. 支付系统中的实践价值

  • 简化客户端逻辑:客户端无需处理分页、过滤等复杂逻辑,通过统一接口遍历,专注于业务处理(如生成结算单)
  • 内存友好:支持按需加载大数据量集合(如超长账单、交易流水),避免内存溢出
  • 适配多数据源:账单数据从数据库迁移到Redis时,只需修改迭代器实现,客户端代码不变
  • 便于扩展:新增遍历方式(如按金额排序遍历)只需添加新的迭代器,无需修改聚合对象和客户端

4. 实践建议

  • 优先使用标准接口:尽量实现java.util.Iterator接口,遵循开发规范,降低学习成本
  • 迭代器不可变:避免在遍历过程中修改聚合对象(如添加/删除元素),否则可能导致迭代异常
  • 支持关闭资源:对于需要释放的资源(如数据库连接),迭代器可实现AutoCloseable接口,确保资源正确释放
  • 按需实现过滤功能:在支付场景中,迭代器可扩展filterByXXX方法,支持按商户、金额等条件过滤,增强实用性

迭代器模式通过“分离聚合与遍历”的思想,为支付系统中复杂集合的遍历提供了优雅解决方案,既保证了聚合对象的封装性,又为遍历操作提供了灵活性,是处理大数据量集合场景的必备设计模式。