本博客大部分内容来于免费在线学习设计模式

1:迭代器模式

迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素。

2:迭代器模式问题

在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。而且不断向集合中添加遍历算法会模糊其 “高效存储数据” 的主要职责。 此外, 有些算法可能是根据特定应用订制的, 将其加入泛型集合类中会显得非常奇怪。

另一方面, 使用多种集合的客户端代码可能并不关心存储数据的方式。 不过由于集合提供不同的元素访问方式, 你的代码将不得不与特定集合类进行耦合。

既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:

  1. 暴露了聚合类的内部表示,使其数据不安全;
  2. 增加了客户的负担。

3:迭代器模式解决方案

迭代器模式的主要思想是将集合的遍历行为抽取为单独的迭代器对象。

所有迭代器必须实现相同的接口。 这样一来, 只要有合适的迭代器, 客户端代码就能兼容任何类型的集合或遍历算法。 如果你需要采用特殊方式来遍历集合, 只需创建一个新的迭代器类即可, 无需对集合或客户端进行修改。

4:迭代器模式结构

20200830122622

  1. 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
  2. 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
  3. 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
  4. 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。
  5. 客户端 (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。
    客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。

示例见Java集合-迭代器

5:迭代器适用场景

  1. 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。
  2. 使用该模式可以减少程序中重复的遍历代码。
  3. 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

6:迭代器优缺点

优点 缺点
单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。
你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
相似的, 你可以暂停遍历并在需要时继续。

7:迭代器模式与其他模式关系

  • 你可以使用迭代器模式来遍历组合模式树。
  • 你可以同时使用工厂方法模式和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
  • 你可以同时使用备忘录模式和迭代器来获取当前迭代器的状态,并且在需要的时候进行回滚。
  • 可以同时使用访问者模式和迭代器来遍历复杂数据结构,并对其中的元素执行所需操作,即使这些元素所属的类完全不同。

8:迭代器模式举例

Java中的集合和迭代器可以作为例子:你可以通过HashMap的keySet().iterator()方法获取一个hashMap的键的迭代器,再通过这个迭代器访问键集合。

9:参考