初识设计模式——迭代器模式(Iterator Pattern)

迭代器模式(Iterator Pattern)是一种行为设计模式,它提供了一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。借助迭代器模式,你能在不了解集合底层实现的情况下遍历集合中的元素。
迭代器模式的构成
- 抽象迭代器(Iterator):定义了访问和遍历元素的接口,包含诸如next()、hasNext()等方法。
- 具体迭代器(Concrete Iterator):实现了抽象迭代器接口,负责具体的遍历操作。
- 抽象聚合(Aggregate):定义了创建迭代器对象的接口。
- 具体聚合(Concrete Aggregate):实现了抽象聚合接口,返回一个具体迭代器的实例。
案例
场景
在 WEB 开发中,假设你有一个博客文章列表,需要对文章进行遍历展示。
代码
1 | // 抽象迭代器 |
UML类图
迭代器模式的适用场景
- 当需要访问一个聚合对象的内容而又无需暴露其内部表示时。
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为不同的聚合结构提供统一的遍历接口时。
迭代器模式的优缺点
优点
- 封装性良好:迭代器模式将遍历逻辑封装在迭代器中,与聚合对象分离,提升了代码的可维护性和可扩展性。
- 支持多种遍历方式:能够为同一个聚合对象提供不同的迭代器,实现多种遍历方式。
- 简化聚合类:聚合类只需专注于存储元素,将遍历操作交给迭代器处理,降低了聚合类的复杂度。
缺点
- 增加类的数量:引入迭代器模式会增加抽象迭代器、具体迭代器等类,使代码结构变得复杂。
- 对新的遍历方式支持有限:若要添加新的遍历方式,可能需要修改迭代器或聚合类的代码。
迭代器模式与for循环的区别
迭代器模式和直接使用for循环都是用于遍历集合元素的方式,但它们在多个方面存在明显差异。
区别
封装性
- 迭代器模式:迭代器模式将遍历逻辑封装在迭代器类中。集合对象只需提供创建迭代器的方法,而具体的遍历操作由迭代器完成。这使得集合的内部结构和遍历逻辑分离,提高了代码的封装性和可维护性。例如,在前面的 PHP 图书馆示例中,BookCollection类只负责管理书籍集合,BookIterator类负责遍历书籍,当集合的内部结构发生变化时,只要迭代器接口保持不变,就不会影响到使用迭代器的代码。
- 直接for循环:直接使用for循环需要在代码中直接访问集合的内部结构,如数组的索引。这意味着代码与集合的具体实现紧密耦合,如果集合的内部结构发生变化,如从数组改为链表,就需要修改for循环的代码。
灵活性
- 迭代器模式:迭代器模式支持多种遍历方式。可以为同一个集合创建不同的迭代器,每个迭代器实现不同的遍历逻辑。例如,对于一个树形结构的集合,可以创建深度优先遍历迭代器和广度优先遍历迭代器。而且,迭代器可以在不改变集合类的情况下,方便地添加新的遍历方式。
- 直接for循环:for循环的遍历方式相对固定,通常是按照索引顺序依次访问元素。如果需要实现不同的遍历方式,就需要修改for循环的代码逻辑,不够灵活。
对集合类型的要求
- 迭代器模式:迭代器模式可以应用于各种类型的集合,无论是数组、链表、树形结构还是其他复杂的数据结构。只要集合类实现了相应的迭代器接口,就可以使用迭代器进行遍历。迭代器隐藏了集合的具体实现细节,使得代码可以统一处理不同类型的集合。
- 直接for循环:for循环通常适用于具有连续索引的数据结构,如数组。对于一些复杂的数据结构,如链表、树形结构,直接使用for循环进行遍历会比较困难,需要手动处理指针或节点的移动。
代码复用性
- 迭代器模式:迭代器类可以被多个地方复用。如果有多个不同的集合需要使用相同的遍历方式,只需要为每个集合创建相应的迭代器实例即可。这样可以避免代码重复,提高代码的复用性。
- 直接for循环:for循环的代码通常是针对特定的集合编写的,难以直接复用。如果需要对不同的集合进行相同的遍历操作,就需要重复编写for循环的代码。
异常处理
- 迭代器模式:迭代器模式可以在迭代器类中统一处理遍历过程中的异常,如越界访问等。这样可以将异常处理逻辑封装在迭代器内部,使调用代码更加简洁。
- 直接for循环:在for循环中,需要在代码中手动处理可能出现的异常,如数组越界。这会使for循环的代码变得复杂,并且容易遗漏异常处理逻辑。
案例
1 | // 迭代器模式示例 |
适合直接使用for循环的场景
- 简单数据结构:遍历数组、列表等线性结构,且遍历逻辑简单(如顺序访问)。
- 高频次遍历或性能敏感场景:对性能要求极高的循环(如算法核心部分)。
- 不需要扩展遍历逻辑:仅需顺序遍历,且未来不会增加新的遍历方式(如倒序、过滤等)。
适合迭代器模式的场景
- 复杂数据结构:遍历链表、树、图等非线性结构,或需要隐藏内部实现细节。
- 需要统一遍历接口:多个不同集合需使用相同遍历逻辑(如日志系统遍历不同存储类型的日志)。
- 支持多种遍历方式:需要动态切换遍历逻辑(如先按 ID 排序,再按时间过滤)。
- 与其他设计模式结合:在工厂模式、组合模式中使用迭代器统一处理元素。
总结
迭代器模式的代码结构更加清晰,将遍历逻辑封装在迭代器类中,而直接for循环则直接操作数组的索引。
场景特征 | 推荐方式 | 理由 |
---|---|---|
简单线性结构,无需扩展 | for 循环 | 代码简洁,性能更高 |
- | - | - |
复杂数据结构或需要解耦 | 迭代器模式 | 隐藏内部细节,支持灵活扩展 |
需要统一遍历接口或复用逻辑 | 迭代器模式 | 通过接口标准化,提升代码复用性 |
性能敏感场景 | for 循环 | 减少对象开销 |
PHP中的实践
- 内置迭代器:PHP 的 ArrayIterator、RecursiveArrayIterator 等可直接用于常见数据结构。
- 延迟加载:迭代器可实现惰性加载(如数据库查询结果分页),避免一次性加载所有数据。
- 与 foreach 结合:实现 Iterator 接口后,可直接使用 foreach 语法糖,简化代码。
PHP中的Iterator接口
在 PHP 里,Iterator接口完全符合迭代器模式的设计规范,它为对象提供了一种自定义遍历行为的途径。当一个类实现了Iterator接口,就能够借助foreach循环对该对象进行遍历,这就如同遍历数组一样。
Iterator接口定义
Iterator接口定义了 5 个必须实现的方法,这些方法构成了迭代器的核心逻辑:
1 | interface Iterator extends Traversable { |
- rewind():把迭代器的指针重置到起始位置,通常在遍历开始时调用。
- current():返回当前指针所指向的元素。
- key():返回当前元素的键(索引)。
- next():将指针移动到下一个元素的位置。
- valid():检查当前指针位置是否有效,也就是是否存在元素。
如何使用
1 | class MyCollection implements Iterator { |
- 初始化:当执行foreach循环时,PHP 会自动调用
$collection->rewind()
,把position重置为 0。
第一次循环: - 调用
$collection->valid()
,因为position为 0,isset($this->items[$this->position])
返回true,所以循环继续。 - 调用
$collection->current()
,返回$this->items[0]
,也就是 1。 - 调用
$collection->key()
,返回$this->position
,即 0。 - 调用
$collection->next()
,将position加 1,变为 1。 - 后续循环:重复步骤 2,直到
$collection->valid()
返回false。当position超出数组范围时,isset($this->items[$this->position])
返回false,循环结束。
通过实现Iterator接口,我们可以自定义对象的遍历逻辑,让对象在foreach循环中表现出特定的行为。