迭代器(Iterator)模式

迭代器(Iterator)模式

隶属类别——对象行为型模式


1. 意图

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

2. 别名

游标(Cursor)

3. 动机

一个聚合对象,如列表(List),应该提供一种方法来让别人可以访问它的元素,而又不需暴露它的内部结构。此外,针对不同的需要,可能要以不同的方式遍历这个列表。但是即使可以预见到所需的那边遍历操作,你可能也不希望列表的接口中充斥着各种不同的遍历操作。有时还可能需要在同一个列表上同时进行多个遍历。

迭代器模式都可以帮你解决所有这些问题。这一模式的关键思想是将对列表的访问和遍历从列表对象中分离开出并放入一个迭代器(Iterator)对象中。迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前的元素;即它知道哪些元素已经遍历过了。

例如,一个列表(List)类可能需要一个列表迭代器(ListIterator),它们的关系如下图所示:

在这里插入图片描述

在实例化的列表迭代器之前,必须地提供待遍历的列表。一旦有了该列表的实例,就可以顺序访问该列表的各个元素。next返回当前表列的当前元素并让index向前加一,haxNext操作判断是否具有下个元素,remove用来移除某个元素。

将遍历机制与列表对象分离使我们可以定义不同的迭代器来实现不同的遍历策略,而无需再列表接口中列举它们。例如,过滤表列迭代器(FilteringListIterator)可能只访问那些满足特定过略约束条件的元素。

注意迭代器和列表是耦合在一起的,而客户对象必须知道遍历的是一个列表而不是其他聚合结构。最好能有一种办法使得不需改变客户代码即可改变该聚合类。可以通过将迭代器的概念推广到多态迭代(polymorphic iteration)来达到这个目标。

例如,讲定我们还有一个列表的特殊实现,比如说SkipList[Pug90].SkipList是一种具有类似于平衡树性质的随机数据结构。我们希望我们的代码对List和SkipList对象都适用。

首先,定义一个抽象列表类AbstractList,它提供操作列表的公共接口。类似地,我们也需要一个抽象的迭代器类Iterator。它定义公共的迭代接口。然后我们可以为每个不同的列表实现定义具体的Iterator子类。这样迭代机制就与具体的聚合类无关了。

在这里插入图片描述

余下的问题就是如何创建迭代器。既然要使这些代码不依赖于具体的列表子类,就不能仅仅简单地实例化一个特定的类,而要列表负责创建相应的迭代器。这需要列表对象提供CreateIterator这样的操作,客户请求调用该操作以获得一个迭代器对象。

创建迭代器是一个Factory Method模式的例子。我们在这里用它来使得一个客户可向一个列表对象请求合适的迭代器。Factory Method模式产生两个类层次,一个是列表,一个是迭代器的。CreateIterator“联系”这两个类层次。

4. 适用性

迭代器模式可用来:

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

5. 结构

在这里插入图片描述

6. 参与者

  • Iterator(迭代器)

    —— 迭代器定义访问和遍历元素的接口

  • ConcreteIterator(具体迭代器)

    —— 具体迭代器实现该接口

    —— 对该聚合遍历是跟踪当前位置

  • Aggregate(聚合)

    —— 聚合定义创建相应迭代器对象的接口

  • ConcreteAggregate(具体聚合)

    —— 具体聚合实现创建聚合中的当前对象,并能够计算出待遍历的后继对象

7. 协作

  • ConcreIterator跟踪聚合中的当前对象,并能够计算出待遍历的后续对象。

8. 效果

迭代器模式有三个优点:

  • 1.它支持以不同的方式遍历一个聚合 复杂的聚合可用多种方式进行遍历。例如,代码生成和语义检查要遍历语法分析树。代码生成可以按中序或者前序来遍历语法分析树。迭代器模式使得改变遍历算法变得很容易:仅需用一个不同的迭代器实例代替原先的实例即可。你也可以自己定义迭代器的子类以支持新的遍历。
  • 2.迭代器简化了聚合的接口 有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了。这样就简化了聚合的接口。
  • 3.在同一个聚合上可以有多个遍历 多个消费者可以从单个生产者获得信息流而不会相互干扰。跟踪流中的位置所需的状态是存储在迭代器对象中,而不是存储在生成器中。每个迭代器保持它自己的遍历状态。因此你可以同时进行多个遍历。

迭代器模式的缺点:

  • 1.如果您需要以某种复杂的方式同时遍历两个不同的数据结构(特别是当一个中的数据确定您在另一个中的位置时),那么迭代器实际上可能会妨碍您。(例子)
  • 2.如果你要更新正在迭代的数据结构,会因为迭代器存储其位置的方式导致不允许更新(实现健壮的迭代器好像可以解决这一问题)。
  • 3.如果在处理列表时需要回溯,那么迭代器可能根本不起作用。

9. 实现

迭代器在实现上有许多变化和选择。下面是一些较重要的实现。实现迭代器模式时通常需要根据所使用的的语言提供的控制结构来进行权衡。一些语言(例如,CLU)甚至可以直接支持这一模式。

  • 1.谁控制该迭代 一个基本的问题是决定由哪一方来控制该迭代,是迭代器还是使用该迭代器的客户。当由客户里控制迭代是,该迭代器称为一个外部迭代器(external iterator),而当有迭代器控制迭代时,该迭代器提供一个内部迭代器(internal iterator)。使用外部迭代器的客户必须主动推进遍历的部分,显式地向迭代器请求下一个元素。相反地,若使用内部迭代器,客户只需向内部迭代器提交一个待执行的操作,而迭代器将对聚合中的每一个元素实施该操作。

    外部迭代器比内部迭代器更灵活。例如,,若要比较两个集合是否相等,这个功能很容易用外部迭代器实现,而几乎无法用内部迭代器实现,但另一方面,内部迭代器的使用较为容易,因为它们以及定义好了迭代逻辑。

  • 2.谁定义遍历算法 迭代器不是唯一可定义遍历算法的地方。聚合本省也可以定义遍历算法,并在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为游标(cursor),因为它仅用来指示当前位置。客户会以这个游标为一个参数调用该聚合的Next操作,而Next操作将改变这个指示器的状态。

    如果迭代器负责遍历算法,那么将易于在相同的聚合上使用不同的迭代算法,同时易于在不同的聚合上重用相同的算法。在另一方面说,遍历算法可能要访问聚合元素的私有变量。如果这样,,将遍历算法放入迭代器中会破坏聚合的封装性。

  • 3.迭代器健壮程度如何 在遍历一个聚合的同时更改这个聚合可能是危险的,如果在遍历聚合的时候增加或删除该聚合元素,可能会导致两次访问同一个元素或者遗漏掉某个元素。一个简单的解决办法是拷贝改变聚合,并对该拷贝实施遍历,但一般来说这样的代价太高。

    一个健壮的迭代器(robust iterator)保证插入和删除操作不会干扰遍历,且不需要拷贝该聚合。有许多方法来实现健壮的迭代器。其中大多数需要向这个聚合注册迭代器。当插入或删除元素是,该聚合要么调整迭代器的内部状态,要么在内部的维护额外的信息以保证正确的遍历.(找到具体的例子?)

    Kofler在ET++中对如何实现健壮的迭代器做了很充分的讨论。Murry讨论了如何为USL StandardComponents列表类实现健壮的迭代器。

  • 4.附加的迭代器操作 迭代器的最小接口由First,Next,isDone和CurrentItem操作组成。其他一些操作也可能很有用。例如,对有序的聚合可用一个Previous操作将迭代器定位到前一个元素。SkipTo(可以重载两种操作一种按具体的值跳转一种按照具体的索引进行跳转)操作用于已排序并做了索引的聚合中,它将迭代器定位到符合指定条件的元素对象上。

  • 5.在Java中使用多态的迭代器 当我们写一个需要以迭代器当作参数的方法时,其实就是在使用多态的迭代。也就是说,我们所写出的代码,可以在不同的集合中游走,只要这个集合支持迭代器即可。我们不在乎这个集合是如何被实现的。但依然可以变成在它的内部游走。

  • 6.迭代器可特权访问 迭代器可被看为创建它的聚合的一个扩展。迭代器和聚合紧密耦合。但是,使用传统的方式的工厂方式会导致聚合只能支持一种遍的方式。为了避免这一问题,迭代器类可包含一些prorected操作来访问(父类是接口啊,方法无法设置为protected)

  • 7.用于组合对象的迭代器 在Composite模式中的那些递归结构上,外部迭代器可能难以实现,因为在结构中不同对象处于嵌套聚合的多个不同层次,因此一个外部迭代器为跟踪当前的对象必须存储一条纵贯该Composite的路径。有时使用一个内部迭代器会更容易一些。它仅需递归地调用自己即可,这样就隐式地将路径存储在调用栈中,而无需显示地维护当前对象位置。

    如果符合的节点有一个接口可以从一个节点移到它的兄弟节点、父节点和子节点,那么基于游标的迭代器是个更好的选择。游标只需跟踪当前的节点;它可依赖这种节点接口来遍历该复合对象。

    复合常常需要用多种方法遍历。前序,后序,中序,以及广度优先遍历都是常用的。你可用不同的迭代器来支持不同的遍历。

  • 8.空迭代器(nullIterator) 一个空迭代器是一个退化的迭代器,它有助于处理边界条件。根据定义,一个NullIterator总是以及完成了遍历,即它的idDone操作或者hasNext()总是返回true,但是它的currentItem或next()总是为空。

    空迭代器使得更容易遍历树形结构的聚合(如复合对象),在遍历过程中的每一个节点。都可向当前的元素请求遍历其各个子节点的迭代器。该聚合元素将返回一个具体的迭代器。但叶子节点元素返回NullIterator的一个实例。这就使我们可以用一种统一的方式实现在整个结构上的遍历。从而便于遍历组合结构。

10. 代码示例

首先是Iterator接口——java.util.Iterator

public interface Iterator<E> {
   	
    boolean hasNext();
    E next();
    default void remove() {
   
        throw new UnsupportedOperationException("remove");
    }
}

接下来是ConcreteIterator—— DinerMenuIterator & java.util.ArrayList中的iterator

DinerMenuIterator.java

public class DinerMenuIterator implements Iterator<MenuItem> {
   
	MenuItem[] items;
	int
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值