【设计模式】二十七、迭代器模式

106 阅读5分钟

这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天,点击查看活动详情

系列文章|源码

github.com/tyronczt/de…

定义-是什么

迭代器模式(Iterator Pattern)又称为游标模式(Cursor Pattern),它提供一种有效的方法来访问一个容器对象中的各个元素,而无需暴露该容器对象的底层实现。该模式的核心是迭代器(Iterator),它是一个对象,用于遍历容器中的元素。

迭代器模式的结构包括以下几个角色:

  1. 抽象迭代器(Iterator):定义了访问和遍历元素的接口,包括访问下一个元素、返回当前元素、判断是否还有下一个元素等方法。
  2. 具体迭代器(ConcreteIterator):实现抽象迭代器接口,具体实现访问和遍历元素的方法。
  3. 抽象容器(Aggregate):定义容器的接口,包括获取迭代器的方法。
  4. 具体容器(ConcreteAggregate):实现抽象容器接口,创建并返回一个具体迭代器实例。

使用迭代器模式可以有效地避免暴露容器对象的底层实现细节,同时提供了一种通用的方式来遍历容器中的元素,使得遍历过程变得更加灵活和可扩展。迭代器模式常用于处理各种集合类型,如数组、链表、树等。

意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

主要解决:不同的方式来遍历整个整合对象。

思考-为什么

优点

  • 单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
  • 开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
  • 你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
  • 相似的, 你可以暂停遍历并在需要时继续。

缺点

  • 如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
  • 对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。

应用-怎么用

应用场景

当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。

迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。

使用该模式可以减少程序中重复的遍历代码。

重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。

如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

该模式为集合和迭代器提供了一些通用接口。 如果你在代码中使用了这些接口, 那么将其他实现了这些接口的集合和迭代器传递给它时, 它仍将可以正常运行。

实现方式

  1. 声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素。 但为了使用方便, 你还可以添加一些其他方法, 例如获取前一个元素、 记录当前位置和判断迭代是否已结束。
  2. 声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口。 如果你计划拥有多组不同的迭代器, 则可以声明多个类似的方法。
  3. 为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立。
  4. 在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
  5. 检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器。

源码应用

JDK中的Iterator源码:

public interface Iterator<E> {
   
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

从上面代码中,我们看到两个主要的方法定义hasNext()和next()方法;

下面接着来看Iterator的实现类,其实在我们常用的ArrayList中有一个内部实现类Itr,它就实现了Iterator接口:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
  ......
  
      private class Itr implements Iterator<E> {
      int cursor; // index of next element to return
      int lastRet = -1; // index of last element returned; -1 if no such
      int expectedModCount = modCount;

      public boolean hasNext() {
        return cursor != size;
      }

      @SuppressWarnings("unchecked")
      public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
          throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
          throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
      }
    }
  
  ......
}

参考

设计模式-迭代器模式学习之旅 - 掘金

迭代器设计模式