1. 从一个极其普遍的编码痛点说起
我做了十几年开发,迭代器模式是我见过最“隐形”、却又最无处不在的设计模式——说它隐形,是因为大多数程序员天天在用,却几乎意识不到自己正踩在迭代器模式的肩膀上。
就拿最基础的需求来说:你有一个列表,里面存了一批订单数据,你需要把它们全部取出来,逐个处理。最原始、最直觉的写法是什么?一个 for 循环加上 get(i)。
java复制for (int i = 0; i < orderList.size(); i++) {
Order order = orderList.get(i);
// 处理订单
}
这段代码有问题吗?在小项目里,没有任何问题。但当你的集合从 ArrayList 换成了 LinkedList,或者换成了 HashSet,甚至换成了存放于数据库游标中的记录集时,问题就来了。不同集合的数据组织方式和访问方式完全不一样,数组有下标,链表没有随机访问能力但可以通过节点指针逐个后移,哈希表干脆是一堆桶结构,数据库游标则是一次性顺序消费……如果你在每个业务代码里都写针对具体集合的遍历细节,那么一旦底层集合替换,所有上层遍历代码全要跟着改。
迭代器模式解决的就是这个核心痛点:把“怎么遍历”和“遍历什么”解耦,让遍历行为对上层使用者完全屏蔽内部结构差异。用一个统一的“游标”概念,把访问逻辑封装进去,集合外部的人只认 hasNext() / next() 这两个动作,至于内部是链表还是数组,根本不关心。
这就是设计模式的魅力——它从来不是在教你怎么写一个新功能,而是在教你怎么把那些反复出现的、普遍存在的结构性问题用成熟的范式去解决。迭代器模式作为 GoF 23 种设计模式中行为型模式的代表之一,它的价值不在于代码本身有多神奇,而在于它定义了“一种统一访问集合内部元素,且不暴露内部表示”的标准做法,而这一标准做法后来几乎被所有现代语言内置吸收。
不管你是 Java、C++、Python、JavaScript 开发者,也不管你是刚学设计模式的新手,还是需要在项目中做架构决策的资深开发,搞清楚迭代器模式的原理和边界,都值回票价。这篇就把我认为最核心的内容拆开了讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 迭代器模式在解决什么问题:一次集合替换引发的连锁修改
迭代器模式的官方定义是:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。定义很精简,但真正理解它还要从它试图解决的问题出发。
2.1 紧耦合的遍历代码是怎么把你拖垮的
假设你维护一个老项目,项目中有一个 OrderManager 类,里面用一个数组保存订单,所有的业务代码都依赖这个数组的遍历方式:
java复制public class OrderManager {
private Order[] orders = new Order[100];
private int count = 0;
// add、delete 等省略
}
半个月后业务量上去了,数组的容量不够用,定长结构导致扩容逻辑写起来痛苦不堪。你决定换成 ArrayList<Order> 或 LinkedList<Order>,性能上各有取舍,但不管换成哪个,所有写过 for (int i = 0; i < manager.getCount(); i++) { Order order = manager.orders[i]; } 的调用方代码,全部要跟着改一遍。
这是最典型的「面向具体实现编程」的代价。乍看之下每次改动都不算大,但随着系统膨胀,这些散落各处的遍历代码会变成一个个隐形的定时炸弹——你永远不知道底层存储换掉之后,还有哪段野代码在用旧的下标去访问。
2.2 迭代器模式给出了怎样的解耦思路
迭代器模式的思路其实异常朴素:既然每种集合都可能有一种最合适的遍历方式,那我就把这些遍历方式各自封装成独立的类,再让它们实现同一个访问接口。外部调用方只依赖这个接口,完全不用关心自己拿到的是数组、链表、哈希表还是自定义数据结构。
打个比方,这有点像你去餐厅点一份烤鸭。不管这家店的烤鸭是果木烤的还是电炉烤的,不管片鸭师傅的刀法是横切还是斜切,你作为顾客只需要知道“端上来的是片好的烤鸭,可以直接卷饼吃”。如果你非要跑进后厨,盯着鸭胚、盯着烤炉、盯着师傅切的每一刀,那只要店里换一种烤法,你的整个“吃饭流程”就崩溃了。迭代器就是那个“端菜的服务员”,它把后厨的复杂度全部隔离掉,给你一个清晰的“吃鸭”界面。
这个朴素的解耦思路价值极大:上层代码不再感知集合内部结构,底层集合替换时,只需配套提供一个新迭代器,所有上层逻辑零改动。用一个形象的代码示意:
java复制Iterator<Order> it = orderManager.createIterator();
while (it.hasNext()) {
Order order = it.next();
// 无论 orderManager 内部是数组还是链表,这层代码完全不变
}
这层稳定、统一、可替换的抽象,就是迭代器模式的核心贡献。聊清楚了它要解决的问题,再来看它的各个成员是如何分工配合的,就顺理成章了。
3. 迭代器模式的核心角色拆解:四个成员,各司其职
GoF 给迭代器模式定义了四个主要角色,理解这四个角色就等于拿到了整个模式的骨架。
- Iterator(迭代器接口):定义访问与遍历元素的统一方法。最经典的是
first()/next()/hasNext()/currentItem(),在现代语言中一般收敛为hasNext()和next()两个核心方法。 - ConcreteIterator(具体迭代器):实现迭代器接口,维护一个“当前位置”的游标,记录遍历进度。这是整个迭代器模式的“大脑”,所有遍历算法细节都封装在这里。
- Aggregate(聚合对象接口):定义创建迭代器的方法,例如
createIterator()。它约定“凡是可以被迭代的集合,都能创建一个对应的迭代器”。 - ConcreteAggregate(具体聚合对象):实现聚合对象接口,内部持有真实的数据结构,同时负责返回一个适配该结构的 ConcreteIterator 实例。
3.1 为什么要把“游标状态”单独抽成一个对象
很多初学者最大的疑问是:为什么不直接在集合类内部维护一个游标,非要多此一举搞一个迭代器对象?
这是迭代器模式最精妙的地方。如果游标是集合内部的字段,那同一时刻多个地方同时遍历这个集合时,游标字段就会被互相覆盖——A 遍历到一半,B 开始遍历,直接把 A 的当前位置冲掉了,A 下一次 next() 就不知道跳到哪去了。把游标独立成迭代器对象,本质上是把“遍历状态”从一个对象中抽离成独立对象,允许多个遍历互不干扰。
我还经常用“浏览器开多个标签页”来类比:集合好比一个网站,内部游标如果存在服务器端,那每个用户共享一个浏览位置,彼此打架;迭代器好比每个用户浏览器里的独立标签页,位置状态各自保存,互相不知道对方看到哪了。
3.2 自己画一张最简单的迭代器架构草图
如果你尝试画一下类关系,会发现这个模式的耦合度极低。聚合接口依赖迭代器接口,具体聚合实现聚合接口并创建具体迭代器,具体迭代器实现迭代器接口并持有一个具体聚合的引用。整个过程中唯一需要被外部见到的就是两个接口,所有实现细节全部封闭在背后。
这种低耦合的架构带来的直接好处有两个:一是新增一种遍历方式不需要修改集合源码,只需要新增一个迭代器实现类,符合开闭原则;二是聚合对象可以同时提供多种不同遍历顺序的迭代器——比如正序遍历、倒序遍历、按条件过滤遍历,只需要提供多个迭代器类即可,集合本身根本不用关心这些策略。
角色理清了,下面直接撸代码,你才能感受到这四个角色是如何落地的。
4. 手写一个迭代器:从零实现能用的可迭代容器
理论说得再多,不如自己动手写一遍。这里我用 Java 写一个最精简但五脏俱全的例子:自定义一个可以存放任意对象的简单列表,并给它配上一个完整的迭代器。这样你能清晰看到四个角色的代码长什么样子。
4.1 定义迭代器接口(Iterator)
尽管 Java 已经提供了 java.util.Iterator,但为了演示迭代器模式的原始结构,我先自定义一个最简单的接口,里面只放最核心的两个方法:
java复制public interface MyIterator<T> {
boolean hasNext();
T next();
}
hasNext() 负责回答“还有没有下一个”,next() 负责取出当前元素并把游标推到下一位。这两个方法共同构成了顺序遍历的最小充分条件。有些教材里还会
