Java 23 种设计模式:从踩坑到精通 | 番外:迭代器模式 —— 物流运单批量处理实战

Java 23 种设计模式:从踩坑到精通 | 番外:迭代器模式 —— 物流运单批量处理实战

Java 23 种设计模式:从踩坑到精通 | 番外:迭代器模式 —— 物流运单批量处理实战

摘要:迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又无需暴露该对象的内部表示。它将遍历行为从聚合对象中分离出来,封装为独立的迭代器对象,让客户端可以用统一的方式遍历不同的集合结构。本文结合物流运单批量处理系统的场景,完整展示如何用迭代器模式统一遍历不同类型的运单集合,并与 for-each 语法糖和数据库游标深度对比,帮你掌握“遍历与数据分离”的设计精髓。

🗺️本文阅读地图(3 分钟速览)

  • 为什么不能直接暴露集合的内部结构?
  • 迭代器核心角色:抽象迭代器、具体迭代器、抽象聚合、具体聚合
  • 手写运单迭代器:统一遍历普通件、加急件、冷链件
  • 迭代器 vs for-each:语法糖的底层原理
  • 面试必问:“IteratorIterable有什么区别?为什么不能在 for-each 中直接删除元素?”

📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 正篇:Iterator 迭代器模式 —— 遍历集合,为什么不直接暴露内部结构? |当前:番外 · 迭代器模式 × 物流运单批量处理
🔗 返回系列总目录


1. 物流运单批量处理的痛点

在物流分拣中心,每天需要批量处理成千上万张运单——普通件、加急件、冷链件。这些运单可能存储在不同的数据结构中:有的用数组、有的用链表、有的用树形结构。如果客户端直接依赖这些底层结构来遍历,一旦数据结构发生变化(如从数组改为链表),所有遍历代码都要修改。

更麻烦的是,不同类型的运单可能需要不同的遍历方式——普通件按顺序处理、加急件需要优先处理、冷链件需要按温度区间分批处理。如果把这些遍历逻辑都写在聚合类中,聚合类会迅速膨胀。

迭代器模式的解决思路:将遍历行为从聚合对象中剥离,封装为独立的迭代器对象。客户端只与迭代器接口交互,完全不关心底层是数组还是链表。同一个聚合对象可以提供多种迭代器——正向遍历、反向遍历、条件过滤遍历——聚合类本身保持简洁。

1.1 你的场景该不该用迭代器?

判断标准是 → 用迭代器否 → 用其他方式
需要隐藏集合的内部结构,不暴露给客户端
需要支持多种遍历方式(正向、反向、条件过滤)
需要统一不同数据结构的遍历接口
结构简单,只需一种遍历方式直接用 for-each 即可

2. 迭代器模式 UML(物流运单批量处理场景)


3. 完整源码实现

3.1 抽象迭代器接口 (Iterator)

/** * 抽象迭代器:定义遍历集合的统一接口 * @param <T> 集合元素类型 */publicinterfaceIterator<T>{booleanhasNext();// 是否还有下一个元素Tnext();// 获取下一个元素voidremove();// 删除当前元素(可选操作)}

💬白话:迭代器必须能回答“还有没有下一个”和“把下一个给我”。remove()是可选操作——不是所有迭代器都需要支持删除。

3.2 抽象聚合接口 (Aggregate)

/** * 抽象聚合:定义创建迭代器的工厂方法 * @param <T> 集合元素类型 */publicinterfaceAggregate<T>{Iterator<T>createIterator();}

💬白话:所有集合都必须能“创建一个迭代器出来”。这就是聚合的唯一职责——工厂方法。

3.3 运单实体类 (Waybill)

/** * 运单实体类 */publicclassWaybill{privateStringwaybillNo;// 运单号privateStringtype;// 类型:普通、加急、冷链privateStringdestination;// 目的地privatedoubleweight;// 重量publicWaybill(StringwaybillNo,Stringtype,Stringdestination,doubleweight){this.waybillNo=waybillNo;this.type=type;this.destination=destination;this.weight=weight;}publicStringgetWaybillNo(){returnwaybillNo;}publicStringgetType(){returntype;}publicStringgetDestination(){returndestination;}publicdoublegetWeight(){returnweight;}@OverridepublicStringtoString(){returnString.format("运单[%s] %s %s %.1fkg",waybillNo,type,destination,weight);}}

💬白话:运单就是被遍历的“元素”——包含运单号、类型、目的地、重量等信息。它不关心自己被怎么遍历,只管存数据。

3.4 具体聚合:运单集合 (WaybillCollection)

importjava.util.ArrayList;importjava.util.List;/** * 具体聚合:运单集合,存储所有待处理的运单 */publicclassWaybillCollectionimplementsAggregate<Waybill>{privateList<Waybill>waybills=newArrayList<>();publicvoidadd(Waybillwaybill){waybills.add(waybill);}publicWaybillget(intindex){returnwaybills.get(index);}publicintsize(){returnwaybills.size();}@OverridepublicIterator<Waybill>createIterator(){returnnewWaybillIterator(this);}/** 获取内部列表(仅供迭代器使用,包私有) */List<Waybill>getWaybills(){returnwaybills;}}

💬白话WaybillCollection就是“聚合”——它只管存运单、提供createIterator()工厂方法。内部用ArrayList存储,但客户端永远不知道。

3.5 具体迭代器:运单迭代器 (WaybillIterator)

/** * 具体迭代器:运单迭代器,负责遍历运单集合 */publicclassWaybillIteratorimplementsIterator<Waybill>{privateWaybillCollectioncollection;privateintindex=0;// 当前遍历位置(游标)publicWaybillIterator(WaybillCollectioncollection){this.collection=collection;}@OverridepublicbooleanhasNext(){returnindex<collection.size();}@OverridepublicWaybillnext(){if(!hasNext()){thrownewIndexOutOfBoundsException("已遍历完所有运单");}returncollection.get(index++);}@Overridepublicvoidremove(){if(index<=0){thrownewIllegalStateException("无法删除,尚未调用next()");}collection.getWaybills().remove(--index);System.out.println(" 🗑️ 已删除当前运单");}}

💬白话:迭代器就像一个“游标”,用index记录当前读到哪里了。hasNext()检查后面还有没有,next()取出当前位置的元素并把游标后移。它是聚合的“内部员工”,可以访问聚合的私有数据,但对外只暴露三个方法。

3.6 客户端测试

publicclassClient{publicstaticvoidmain(String[]args){// 1. 创建运单集合(聚合对象)WaybillCollectioncollection=newWaybillCollection();// 2. 添加运单数据collection.add(newWaybill("SF20240723001","普通件","北京",2.5));collection.add(newWaybill("SF20240723002","加急件","上海",1.2));collection.add(newWaybill("SF20240723003","冷链件","广州",5.0));collection.add(newWaybill("SF20240723004","普通件","深圳",3.8));System.out.println("=== 场景:物流分拣中心遍历处理运单 ===\n");// 3. 通过聚合对象创建迭代器(客户端不关心内部存储结构)Iterator<Waybill>iterator=collection.createIterator();// 4. 统一遍历处理intcount=1;while(iterator.hasNext()){Waybillwaybill=iterator.next();System.out.println("📦 处理第 "+count+" 个运单:"+waybill);// 模拟特殊处理逻辑if("冷链件".equals(waybill.getType())){System.out.println(" ❄️ 冷链件特殊处理:转入冷藏区");}elseif("加急件".equals(waybill.getType())){System.out.println(" ⚡ 加急件特殊处理:优先分拣通道");}count++;}System.out.println("\n=== 所有运单处理完毕,共计 "+collection.size()+" 单 ===");}}

💬白话:客户端只需要拿到迭代器,用hasNext()+next()的标准写法遍历所有运单。如果将来WaybillCollection内部从ArrayList改为链表,客户端代码一行都不用改。

4. 运行结果

=== 场景:物流分拣中心遍历处理运单 === 📦 处理第 1 个运单:运单[SF20240723001] 普通件 北京 2.5kg 📦 处理第 2 个运单:运单[SF20240723002] 加急件 上海 1.2kg ⚡ 加急件特殊处理:优先分拣通道 📦 处理第 3 个运单:运单[SF20240723003] 冷链件 广州 5.0kg ❄️ 冷链件特殊处理:转入冷藏区 📦 处理第 4 个运单:运单[SF20240723004] 普通件 深圳 3.8kg === 所有运单处理完毕,共计 4 单 ===

5. 核心角色回顾

角色职责对应代码
Iterator定义遍历元素的接口Iterator<T>
ConcreteIterator维护遍历位置,实现遍历逻辑WaybillIterator
Aggregate定义创建迭代器的工厂方法Aggregate<T>
ConcreteAggregate存储数据,创建具体迭代器WaybillCollection

6. 迭代器模式 vs for-each 语法糖

// for-each 写法(语法糖)for(Waybillwb:collection){System.out.println(wb);}// 编译后等价代码Iterator<Waybill>it=collection.iterator();while(it.hasNext()){Waybillwb=it.next();System.out.println(wb);}
对比项迭代器模式for-each 语法糖
本质设计模式,手动控制遍历编译器自动展开为迭代器循环
删除元素✅ 支持iterator.remove()❌ 直接list.remove()会抛异常
灵活度高,可自定义遍历逻辑低,只能顺序遍历

💡一句话记忆:你每天都在用 for-each,但它的底层就是迭代器模式。Iterable是“能返回迭代器的对象”,Iterator是“迭代器本身”。

7. 迭代器模式的优缺点

优点缺点
封装内部结构,客户端统一遍历增加类数量
支持多种遍历方式(正向/反向/过滤)简单集合可能过于复杂
符合单一职责:聚合存数据,迭代器管遍历遍历时修改集合可能抛异常

8. 六大设计原则体现

原则体现
单一职责聚合只存储,迭代器只遍历
开闭原则新增遍历方式只需增加迭代器子类
里氏替换所有迭代器可替换Iterator接口
依赖倒置客户端依赖抽象IteratorAggregate
接口隔离Iterator只有三个方法
迪米特法则客户端只与迭代器交互,不知聚合内部结构

附 迭代器模式 UML源码(物流运单批量处理场景)

@startuml title Java 23 种设计模式:从踩坑到精通 footer 折哥 | 智能物流与Java实战 ' 1. 全局样式配置 skinparam backgroundColor #FEFEFE skinparam shadowing false skinparam classBorderColor #333333 skinparam classFontColor #1A1A1A skinparam classFontSize 14 skinparam noteFontSize 12 skinparam noteFontColor #555555 skinparam arrowColor #555555 skinparam classBackgroundColor #F9F9F9 skinparam interface { BackgroundColor #E8F5E9 BorderColor #2E7D32 } ' 2. 抽象迭代器 interface Iterator { + hasNext() : boolean + next() : Object + remove() } note right of Iterator <b>抽象迭代器</b> -- 定义遍历集合的统一接口 封装遍历算法,与具体聚合解耦 end note ' 3. 具体迭代器 class ConcreteIterator implements Iterator { - aggregate : ConcreteAggregate - index : int + hasNext() : boolean + next() : Object + remove() } note right of ConcreteIterator <b>具体迭代器</b> -- 维护当前遍历位置(index) 持有聚合对象的引用 实现具体的遍历逻辑 end note ' 4. 抽象聚合 interface Aggregate { + createIterator() : Iterator } note right of Aggregate <b>抽象聚合</b> -- 定义创建迭代器的工厂方法 客户端通过此接口获取迭代器 end note ' 5. 具体聚合 class ConcreteAggregate implements Aggregate { - items : List + createIterator() : Iterator + add(item) + get(int) : Object + size() : int } note right of ConcreteAggregate <b>具体聚合</b> -- 存储实际的数据集合 实现创建具体迭代器的方法 end note ' 6. 关系连线 ConcreteIterator ..|> Iterator : 实现 ConcreteAggregate ..|> Aggregate : 实现 ConcreteAggregate ..> ConcreteIterator : 创建 ConcreteIterator --> ConcreteAggregate : 持有引用 @enduml

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

  • 开篇:系列介绍与目录
  • 正篇:Iterator 迭代器模式—— 遍历集合,为什么不直接暴露内部结构?
  • 当前:番外 · 迭代器模式 × 物流运单批量处理(你在这里)
  • 创建型模式汇总
  • 结构型模式汇总
  • 行为型模式汇总

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。