在软件开发中,我们经常需要处理各种形式的对象集合。无论是简单的数组、动态列表,还是复杂的树形结构或图,我们经常面临一个共同的需求:如何不暴露集合的底层实现细节,又能方便地遍历其中的每一个元素?
如果我们将遍历逻辑与集合本身紧密耦合,一旦底层数据结构发生变化(例如从数组改为链表),客户端代码就不得不进行修改。这不仅增加了维护成本,也违反了“开闭原则”。
在本文中,我们将深入探讨迭代器设计模式。我们将学习它如何将“遍历”的职责从“集合对象”中分离出来,从而提供一种统一、简洁的访问方式。我们还将通过多个实战代码示例,从基础实现到最佳实践,彻底掌握这一行为型设计模式。
什么是迭代器模式?
迭代器模式是一种行为型设计模式,它允许你顺序访问一个聚合对象中的元素,而又不需要暴露该对象的底层表示。简单来说,它提供了一种方法来顺序访问聚合对象中的元素,而不需要知道聚合对象的内部结构。
核心思想
想象一下,你正在阅读一本书。你并不需要知道书是如何装订的,也不需要知道页码在物理上是如何存储的,你只需要从第一页翻到下一页,直到读完最后一页。迭代器模式就是那个“书签”或者“翻页的手势”,它帮你管理当前阅读的位置(游标),并为你提供“下一个内容”的方法。
这种模式在我们的代码中非常常见,以至于它已经内置于大多数现代编程语言的标准库中(例如 Java 的 Iterator,Python 的循环迭代,C++ 的 STL 迭代器)。
为什么要使用迭代器模式?(问题陈述)
假设我们正在构建一个企业级的应用程序,需要维护一个通知列表。随着业务的发展,你的代码中有很多地方都需要遍历所有通知来发送邮件或更新状态。
最初,为了简单起见,我们将这些通知存储在一个简单的数组中。遍历代码可能如下所示:
// 假设 notificationList 是一个数组
for (int i = 0; i < notificationList.length; i++) {
Notification notification = notificationList[i];
notification.send();
}
遇到的挑战
过了一段时间,由于通知数量激增,数组不再能满足性能需求(例如数组插入删除效率低),你决定将底层数据结构从数组改为动态列表,或者更复杂的哈希表甚至自定义树形结构。
这时,问题来了:
- 客户端代码崩溃:原来的 INLINECODE36af9fa8 和 INLINECODEe852fe66 索引访问方式可能不再适用。如果你有 50 处代码使用了这种遍历方式,你需要修改这 50 处代码。
- 暴露实现细节:客户端代码必须知道底层是数组还是列表,这破坏了封装性。
- 多重遍历困难:如果你想同时支持“正序遍历”和“倒序遍历”,或者只遍历“未读通知”,直接修改集合类会让它变得臃肿不堪。
解决方案
我们可以使用迭代器模式来解决这个问题。通过定义一个单独的“迭代器”对象来封装遍历逻辑,无论底层数据结构如何变化,只要迭代器的接口不变,客户端代码就无需修改。
迭代器模式的组成部分
为了实现迭代器模式,我们需要定义几个关键的组件。这就像是组装一台机器,每个零件各司其职。
以下是该模式的类图结构:
- 迭代器接口
这是核心契约。它定义了访问和遍历元素所需的方法。通常包括:
* next():返回下一个元素。
* hasNext():判断是否还有剩余元素。
* (可选)remove():从集合中移除当前元素。
- 具体迭代器
它负责实现迭代器接口,并跟踪当前的遍历位置。它知道如何遍历具体的聚合对象。
- 聚合接口
它定义了一个创建迭代器对象的方法,例如 createIterator()。这样做使得客户端代码不依赖于具体的聚合类。
- 具体聚合
这是具体的集合类(如列表、数组等)。它实现了聚合接口,并返回对应的具体迭代器实例。
实战示例 1:员工薪资计算系统
让我们通过一个具体的例子来实现这个模式。假设我们有一家公司的员工列表,存储在不同的集合中,我们需要计算所有员工的总工资。
我们的目标是:编写客户端代码时,不需要知道员工是存储在数组、列表还是其他数据结构中。
步骤 1:定义迭代器接口
首先,我们定义一个通用的迭代器接口。为了保证类型安全,这里使用了 Java 泛型 `INLINECODEd134d831ListINLINECODE3768a280ListINLINECODEaf4e4d2dEmployeeIteratorINLINECODEf5e3363bReverseIteratorINLINECODEedf40864next()INLINECODEf9e55ad6ConcurrentModificationExceptionINLINECODEf07b8a57modCountINLINECODE33011372next()INLINECODE96b5912bfor-eachINLINECODE8474add1getIterator()` 方法。这将是重构代码迈出的优秀一步。