5.4-Iterator「cursor」-迭代器-行为型模式

目的

提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

示例

一个聚合对象,如列表(list),需要:

  • 提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构
  • 可能要以不同的方式遍历
  • 不希望列表的接口中充斥着各种不同遍历的操作

KEY:将对列表的访问和遍历从列表对象中分离出来并放入一个迭代器(iterator)对象中,由迭代器对象跟踪当前元素,维护遍历序列

  • 将遍历机制与列表对象分离使我们可以对同一个容器定义不同的迭代器来实现不同的遍历策略。例如先序迭代和后序迭代

多态迭代

  • 为何需要多态:上述方法中,迭代器和列表是耦合在一起的,而且客户对象必须知道遍历的是一个列表而不是其他聚合结构
  • 方法:使用继承和多态机制,并由容器提供 CreateIterator 的接口,创建自己兼容的某种迭代器
    • 这是 Factory Method,产生两个类层次,一个是列表的,一个是迭代器的。 CreateIterator“联系”这两个类层次

适用性

  • 访问一个聚合对象的内容而无须暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)。

结构

  • Iterator:定义访问和遍历元素的接口
  • ConcreteIterator:
    • 实现迭代器接口
    • 对该聚合遍历时跟踪当前位置,并能够计算出待遍历的后继对象
  • Aggregate(聚合):定义创建相应迭代器对象的接口。
  • ConcreteAggregate:实现创建相应迭代器的接口,该操作返回 ConcreteIterator 的一个适当的实例

优缺点

迭代器模式有三个重要的作用:

  • 支持以不同的方式遍历一个聚合
  • 简化了聚合的接口:有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了
  • 在同一个聚合上可以同时有多个遍历:每个迭代器保持它自己的遍历状态,因此你可以同时进行多个遍历

实现

谁控制迭代

控制迭代即控制迭代的推进过程,根据迭代过程是迭代器还是使用该迭代器的客户控制,迭代器可分为内部/外部迭代器 2 类:

  • 外部迭代器:客户必须主动推进遍历的步伐,显式地向迭代器请求下一个元素
  • 内部迭代器:由迭代器控制迭代的推进,客户只需向其提交一个待执行的操作,而迭代器将对聚合中的每一个元素实施该操作。

谁定义遍历算法

迭代器和聚合均可定义遍历算法

聚合定义遍历算法聚合定义遍历算法,迭代器只存储当前迭代的上下文

  • 在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为游标(cursor),因为它仅用来指示当前位置。
  • 客户会以这个游标为参数调用该聚合的 Next 操作,而 Next 操作将改变这个指示器的状态

迭代器定义遍历算法

  • 优点:
    • 易于在相同的聚合上使用不同的迭代算法
    • 易于在不同的聚合上复用相同的算法
  • 缺点:遍历算法可能需要访问聚合的私有变量。如果这样,将遍历算法放入迭代器中会破坏聚合的封装性

遍历时修改

在遍历一个聚合的同时更改这个聚合可能是危险的。如果在遍历聚合的时候增加或删除聚合元素,可能会导致两次访问同一个元素或者遗漏掉某个元素。

一个健壮的迭代器 robust iterator 保证==插入和删除操作不会干扰遍历,且不需要拷贝该聚合==

  • 这大多数需要向聚合注册迭代器。当插入或删除元素时,该聚合需要:
    • 调整迭代器的内部状态
    • 或在内部维护额外的信息以保证正确的遍历。

可选接口

迭代器的最小接口由 First、Next、IsDone 和 CurrentItem 操作组成

对有序的聚合:Previous 操作将迭代器定位到前一个元素 对于可以随机访问的聚合:SkipTo 操作将迭代器定位到符合指定条件的元素对象上

多态的迭代器

多态迭代器是有以下两种缺点

  • 其要求用一个 Factory Method 动态地分配迭代器对象。因此仅当必须多态时才使用它,否则使用在栈中分配内存的具体的迭代器
  • 客户必须负责删除它(这容易导致错误,因为你容易忘记释放一个使用堆分配的迭代器对象)
    • 解决方法;我们可使用一个栈分配的 Proxy 作为实际迭代器的中间代理。该代理在其析构器中删除迭代器。这样当该代理的生命周期结束时,实际迭代器将同它一起被释放
    • 这是著名的 C++“资源分配即初始化”技术的应用实例

迭代器和聚合

迭代器可被看作创建它的聚合的一个扩展,两者紧密耦合。在 C++中可以如此实现:

  • 将迭代器作为聚合的友元,从而避免在聚合类中定义迭代操作
    • 使定义新的遍历变得很难,因为它将要求改变该聚合的接口增加另一个友元
    • 解决方法:==友元+继承==。迭代器类可包含一些 protected 操作来访问聚合类的重要的非公共可见的成员。迭代器子类(且只有迭代器子类)可使用这些 protected 操作来得到对该聚合的特权访问。

用于组合对象的迭代器

Composite(4.3)模式中的递归聚合结构上:

  • 外部迭代器可能难以实现,因为在该结构中不同对象处于嵌套聚合的多个不同层次,因此一个外部迭代器为跟踪当前的对象必须存储一条纵贯该 Composite 的路径
  • 内部迭代器会更容易一些:它仅需要递归地调用自己

如果组合中的结点有一个接口可以从一个结点移到它的兄弟结点、父结点和子结点,则可选用游标迭代

空迭代器

空迭代器用于处理边界条件,其 isDone 恒为真

例如,可以简化树形结构的聚合的遍历

  • 在遍历过程中的每一个结点,都可向当前的元素请求遍历其各个子结点的迭代器
  • 叶结点元素返回一个 NullIterator