迭代器模式适合哪些应用场景?
简化版
迭代器模式适合需要顺序访问一组元素,但不希望暴露内部结构的场景。常见应用包括集合框架、树结构遍历、分页游标、文件逐行读取、组合对象遍历和数据库结果集遍历。
详细版
适合迭代器模式的场景通常有几个特征:
- 内部结构不希望暴露给客户端。
- 遍历逻辑比较复杂或可能变化。
- 同一个集合需要支持多个独立遍历过程。
- 需要统一遍历不同类型集合。
典型例子:
- Java 集合的
Iterator。 - 树结构的前序、中序、层序遍历。
- 分页接口或游标查询。
- 文件读取器逐行返回内容。
- 组合模式中的树形菜单遍历。
完整版教学
一、集合框架
集合框架是迭代器模式最典型的应用。
ArrayList、LinkedList、HashSet、TreeSet 的内部结构完全不同,但都能通过 Iterator 遍历。
客户端不需要根据集合类型写不同循环。统一接口让集合使用更加一致。
二、树结构遍历
树结构有多种遍历方式:
- 前序遍历。
- 中序遍历。
- 后序遍历。
- 层序遍历。
如果把这些逻辑全写在客户端,代码会重复且容易出错。可以为树提供不同迭代器,让遍历策略独立变化。
例如表达式树、组织架构树、菜单树、文件目录树都适合这种思路。
三、分页和游标查询
有些数据不能一次性加载完,例如数据库大表、远程 API 列表、日志流。
可以用迭代器思想把“取下一页”封装起来:
- 当前页游标。
- 是否还有更多。
- 获取下一批数据。
客户端只像遍历集合一样消费数据,不需要关心分页参数怎么传。
四、文件逐行读取
文件读取也可以看成迭代过程。
每次调用取下一行,直到文件结束。调用方不需要一次性把整个文件加载到内存,也不需要直接操作底层缓冲区。
这种迭代方式适合大文件处理。
五、组合模式中的树形对象遍历
组合模式里经常有树形结构,例如文件夹和文件、菜单和菜单项。
如果要遍历整棵树,可以为组合对象提供迭代器。客户端只关心访问每个节点,不关心递归细节。
这也是迭代器模式和组合模式经常一起出现的原因。
六、数据库结果集
数据库查询结果也常体现迭代器思想。
例如 JDBC 的 ResultSet 通过 next() 向后移动游标,再读取当前行字段。调用方不需要知道数据库驱动如何拉取数据。
严格说 ResultSet 不只是设计模式示例,它还包含数据库游标、网络通信、驱动实现等机制。但从访问方式上看,它体现了迭代器思想。
七、不适合的场景
不适合迭代器模式的情况包括:
- 数据结构非常简单且只在一个地方遍历。
- 客户端本来就需要深度控制内部结构。
- 遍历过程和业务规则强绑定,无法抽离。
- 引入迭代器后反而让代码更绕。
模式要服务于封装和复用,不是所有循环都要抽成迭代器。
八、常见误区与追问
迭代器适用于需要顺序访问而不暴露存储结构的场景。一个树集合可分别提供深度优先和广度优先2种迭代器,客户端仍使用同一接口;分页 API 也能包装成按需拉取的惰性迭代器。若数据只有固定3项且遍历逻辑只用一次,额外抽象未必有价值。
| 检查维度 | 判定依据 |
|---|---|
| 高匹配 | 多种结构或遍历策略,需要统一访问 |
| 低匹配 | 固定局部数据且没有封装需求 |
Collection -> DFS Iterator | BFS Iterator
心法:当客户端关心“依次拿到元素”而不该关心“元素怎么存”时使用。
- 误区:迭代器只适合内存集合。 文件流、数据库游标和分页接口都可提供惰性迭代。
- 追问:分页迭代器要注意什么? 网络失败、页令牌失效和重复拉取都要有明确语义。
- 误区:树只能有一种正确遍历顺序。 前序、后序、层序等策略可由不同迭代器封装。
- 追问:无限序列能使用迭代器吗? 可以惰性生成,但客户端必须自行设置停止条件。
- 追问:什么时候 Stream 更合适? 需要映射、过滤、聚合流水线且操作无副作用时。
九、加强记忆
迭代器模式适合“有一堆元素要访问,但不想暴露它们怎么存、怎么走”的场景。集合、树、文件、分页、结果集,本质都是统一遍历接口的应用。