什么是迭代器模式(Iterator Pattern)

迭代器模式提供一种顺序访问聚合对象(Aggregate, 如列表、集合、数组等)中各个元素的方法,而无需暴露该对象的内部表示
想象一下你在图书馆浏览书架(聚合对象 Aggregate):
  • 你可以从书架的一端开始,一本一本地查看书籍(访问元素)。
  • 你不需要知道书架内部是如何精确排列或存储这些书的(是按字母顺序、分类号还是随机堆放?)。
  • 你只需要一个简单的方法:“给我下一本书”(next() 操作),以及知道“后面还有没有书了?”(hasNext() 操作)。
这个“给我下一本书”并“判断还有没有”的机制,就是迭代器的核心思想。它将遍历聚合对象的责任从聚合对象本身分离出来,交给了专门的迭代器对象。
迭代器模式的核心在于提供一个统一的接口来遍历不同的聚合结构,同时隐藏其内部细节。

核心思想

  1. 定义迭代器接口 (Iterator): 定义访问和遍历元素所需的操作接口。最核心的方法通常是:
      • hasNext(): Boolean: 检查序列中是否还有下一个元素。
      • next(): ElementType: 返回序列中的下一个元素,并将迭代器的内部指针向后移动。
      • (可选) remove(): 从底层聚合对象中移除 next() 方法最后返回的那个元素(这个方法比较复杂,不是所有迭代器都支持)。
  1. 创建具体迭代器 (ConcreteIterator): 实现 Iterator 接口。它负责跟踪遍历过程中的当前位置,并知道如何从特定的聚合对象 (ConcreteAggregate) 中获取下一个元素。它通常需要持有对它所遍历的聚合对象的引用。
  1. 定义聚合接口 (Aggregate): 定义一个接口,包含一个用于创建并返回一个 Iterator 对象的方法,通常命名为 createIterator() 或 iterator()。
  1. 创建具体聚合 (ConcreteAggregate): 实现 Aggregate 接口。它持有实际的元素集合(如使用数组、列表、哈希表等存储)。它实现了 createIterator() 方法,返回一个与其内部结构相对应的 ConcreteIterator 实例。
  1. 客户端使用: 客户端代码通过 Aggregate 接口获取 Iterator 对象,然后使用 Iterator 接口的方法(hasNext(), next())来遍历聚合中的元素,而无需关心聚合的具体类型或内部实现。

优点

  1. 支持多种遍历方式: 可以为同一个聚合提供不同的迭代器(如正向迭代器、反向迭代器、过滤迭代器)。
  1. 简化聚合接口: 聚合对象本身不需要包含复杂的遍历方法,其接口更简洁,职责更单一(只负责存储和管理元素)。
  1. 统一遍历接口: 客户端可以使用相同的接口 (Iterator) 来遍历不同的聚合结构 (List, Set, Array, 自定义结构),使客户端代码更通用。
  1. 解耦: 将遍历算法与聚合对象的内部结构解耦。聚合的内部表示可以改变,只要它能提供一个有效的迭代器,客户端的遍历代码就不受影响。
  1. 并发遍历: 可以同时在同一个聚合对象上拥有多个独立的迭代器,每个迭代器维护自己的遍历状态。

可能的缺点

  1. 增加类的数量: 对于简单的聚合结构,引入单独的迭代器类可能会显得有些“重”(增加类的数量)。
  1. 效率考量 (通常不显著): 相对于直接访问(如数组索引),通过迭代器访问可能存在微小的性能开销,但在大多数情况下这种开销可以忽略不计,且被其带来的灵活性所弥补。

使用场景

  1. 当你需要访问一个聚合对象的内容,而不想暴露它的内部结构时。
  1. 当你需要为聚合对象支持多种遍历方式时。
  1. 当你需要提供一个统一的接口来遍历各种不同的聚合结构时。
  1. 几乎所有的集合类库: 这是迭代器模式最广泛的应用。Java 的 java.util.Iterator 和 java.lang.Iterable,Kotlin 的 kotlin.collections.Iterator 和 kotlin.collections.Iterable 都是该模式的体现。for-each 循环(如 Java 的 for (Element e : collection) 或 Kotlin 的 for (e in collection)) 在底层就是使用了迭代器。
  1. 数据库游标 (Cursor): 数据库查询结果集返回的 Cursor 对象扮演了迭代器的角色,允许你逐行访问查询结果,而无需一次性加载所有数据到内存。
  1. 流式处理 (Streams): 像 Java Streams 或 Kotlin Sequences 这样的流式 API,其操作也依赖于迭代器的思想来按需处理元素。
  1. Android 中的场景:
      • 遍历 Cursor: 从 ContentProvider 或 SQLiteDatabase 查询返回的 Cursor,使用 moveToNext(), getType(), getString() 等方法遍历数据,这就是迭代器模式的应用。
      • 遍历 ViewGroup 的子 View: 虽然不直接提供标准的 Iterator 接口,但你可以通过 getChildCount() 和 getChildAt(index) 来手动实现类似迭代器的遍历逻辑,或者某些库可能提供迭代器封装。
      • 遍历 SharedPreferences: 获取 SharedPreferences.getAll().keys 返回一个 Set<String>,你可以使用其迭代器来遍历所有的 key。
      • 任何自定义集合: 如果你创建了自己的数据集合类,并希望它能被方便地遍历(例如用在 for-in 循环中),你应该让它实现 Iterable 接口并提供一个 Iterator。

UML 图

下面是基于一个简单的“书架 (BookShelf)”示例的迭代器模式 UML 图:
  • Iterator<T> (Interface): 定义了 hasNext() 和 next()。
  • Aggregate<T> (Interface): 定义了 createIterator()。
  • Book: 简单的领域对象。
  • BookShelf (ConcreteAggregate): 实现了 Aggregate<Book>。内部使用 MutableList 存储 Book。createIterator() 返回一个 BookShelfIterator 实例。
  • BookShelfIterator (ConcreteIterator): 实现了 Iterator<Book>。持有对 BookShelf 的引用,并维护当前遍历的 index。
  • Client: 获取 BookShelf 的迭代器,并使用 while(iterator.hasNext()) 循环来遍历和打印书籍信息。

代码示例

在这个例子中:
  • 我们定义了 BookShelf (Aggregate) 和 BookShelfIterator (Iterator)。
  • BookShelf 提供了 createIterator 方法来获取迭代器。
  • BookShelfIterator 维护了当前遍历的位置 (index) 并实现了 hasNext 和 next。
  • 客户端代码通过调用 createIterator 获得迭代器,并使用标准的 while 循环和 hasNext/next 来遍历书架,完全不需要知道 BookShelf 内部是用数组存储的。
  • 我们还对比说明了如果实现 Kotlin 标准的 Iterable 接口,就可以直接使用更简洁的 for-in 循环,这是因为 for-in 语法糖底层就是依赖 iterator() 方法获取迭代器来工作的。

总结

  • 迭代器模式提供了一种标准且解耦的方式来顺序访问聚合对象的元素,而无需暴露其内部实现细节
  • 它将遍历的责任从聚合对象中分离出来,使得两者都可以独立变化。
  • 这是集合框架的基础,使得我们可以用统一的方式(如 for-in 循环)处理各种不同的数据集合。
💡
记住它的核心:想遍历?找迭代器!它带路,不问集合内部事。
 
设计模式——行为型-备忘录模式设计模式——行为型-策略模式
Loading...