迭代模式(Iterator Pattern 游标模式 CursorPattern,对象行为型模式)

来源:互联网 发布:asp 网页访问数据库 编辑:程序博客网 时间:2024/06/01 09:13

意图

提供一种方法访问一个容器【聚合】(Container)对象中的各个元素,而不需暴露该对象的内部细节。

适用性

迭代器模式可用来:
1. 访问一个聚合对象的内容而无需暴露它的内部表示。
2. 支持对聚合对象的多种遍历
3. 为遍历不同的聚合结构提供一个统一的接口(即,支持多态迭代)

结构

这里写图片描述

参与者

Iterator

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

ConcreteIterator

  1. 具体迭代器实现迭代接口
  2. 对该聚合遍历时跟踪当前位置

Aggregate(Container)

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

ConcreteAggregate(ConcreteContainer)

  1. 具体聚合实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例。

协作

  1. ConcreteIterator跟踪聚合中的当前对象,并能够计算出遍历的后继对象。

效果

支持以不同的方式遍历一个聚合

复杂的聚合可用多种方式进行遍历。例如,代码生成和语义检查要遍历语法分析树。代码生成可以按中序或者按前序来遍历语法分析树。迭代器模式使得改变遍历算法变得很容易:仅需用一个不同的迭代器的实例替代原先的实例即可。你也可以定义迭代器的子类以支持新的遍历。

迭代器简化了聚合的接口

有了迭代器的遍历接口,聚合本身就不在需要类似的遍历接口了。这样就简化了聚合的接口。

在用一个聚合上可以有多个遍历

每个迭代器保持它自己的遍历状态。因此你可以同时进行多个遍历。

优点

  1. 支持以不同的方式遍历一个容器角色。根据实现方式不同,效果上会有差别
  2. 简化了容器的接口。但是java collection中为了提高可扩展性,容器还提供了遍历的接口
  3. 对于同一个容器对象,可以同时进行多个遍历。因为遍历状态是保存在每个迭代器对象中的。

缺点

对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐

实现

谁控制该迭代

一个基本的问题是决定由哪一方来控制该迭代,是迭代器还是使用该迭代的客户。当由客户来控制迭代时,该迭代器称为一个外部迭代器,而当由迭代器控制迭代时,该迭代器称为一个内部迭代器。使用外部迭代器的客户必须主动推进遍历的步伐,显示地向迭代器请求下一个元素。相反地,若使用内部迭代器,客户只需要向其提交一个待执行的操作,而迭代器将对聚合中的每一个元素实施该操作。
外部迭代器比内部迭代器更灵活。当另一方面,内部迭代器的使用较为容易,因为它们已经定义好了迭代逻辑。

谁定义遍历算法

迭代器不是唯一可定义遍历算法的地方。聚合本身也可以定义遍历算法,并在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为一个游标,因为它仅用来指示当前位置。客户会以这个游标为一个参数调用聚合的Next的操作,而Next操作将改变这个指示器的状态。
如果迭代器负责遍历算法,那么将易于在相同的聚合上使用不同的迭代算法,同时也易于在不同的聚合上重用相同的算法。从另一个方面说,遍历算法可能需要访问聚合的私有变量。如果这样,将遍历算法放入迭代器中会破坏聚合的封装性。

迭代器健壮程度如何

在遍历一个聚合的同时更改这个聚合可能是危险的。如果在遍历聚合的时候增加或删除该聚合元素,可能会导致两次访问同一个元素或者遗漏掉某个元素。一个简单的解决办法是拷贝该聚合,并对该拷贝实施遍历,但一般来说这样做代价太高。
一个健壮的迭代器保证插入和删除操作不会干扰遍历,且不需要拷贝聚合。有许多方法来实现健壮的迭代器。其中大多数需要向这个聚合注册该迭代器。当插入或删除元素时,该聚合要么调整迭代器的内部状态,要么在内部的维护额外的信息以保证正确的遍历。

附加的迭代器操作

迭代器的最小接口由Fisit、Next、IsDone和CurrentItem操作组成。其他一些操作可能也很有用。例如,对有序的聚合可用一个Previous操作将迭代器定位到前一个元素。SkipTo操作用于已排序并做了索引的聚合中,它将迭代器定位到符合指定条件的元素对象上。

在C++中使用多态的迭代器

使用多态迭代器是有代价的。它们要求用一个Factory Method动态的分配迭代器对象。因此仅当必须多态才使用它们。否则使用在栈中分派内存的具体迭代器。
多态迭代器有另一个缺点:客户必须负责删除它们。这容易导致错误,因为你容易忘记释放一个使用堆分配的迭代器对象,当一个操作有多个出口时尤为如此。而且期间如果有异常被触发,迭代器对象将永远不会被释放。
Proxy模式提供了一个补救方法。我们可使用一个栈分配的Proxy作为实际迭代器的中间代理。该代理在其析构器中删除该迭代器。这样当该代理生命周期结束时,实际迭代器将同它一起被释放。即使是在发生异常时,该代理机制能保证正确地清除迭代器对象。

迭代器可有特权访问

迭代器可被看为创建它的聚合的一个扩展。迭代器和聚合紧密耦合。在C++中我们可让迭代器作为它的聚合的一个友元来表示这种紧密的关系。这样你就不需要在聚合类中定义一些仅为迭代器所使用的操作。
但是,这样的特权访问可能使定义新的遍历变得很难,因为它将要求改变该聚合的接口增加另一个友元。为避免这一问题,迭代器可包含一些protected操作来访问聚合类的重要的非公共可见的成员。迭代器子类(且只有迭代器子类)可使用这些protected操作来得到对该聚合的特权访问。

用于符合对象的迭代器

在Composite模式中那些递归聚合结构上,外部迭代器可能难以实现,因为在该结构中不同对象处于嵌套聚合的多个不同层次,因此一个外部迭代器为跟踪当前的对象必须存储一条纵贯该Composite的路径。有时使用一个内部迭代器会更容易一些。它仅需递归地调用自己即可,这样就隐式得将路径存储在调用栈中,而无需显式地维护当前对象位置。
如果符合中的节点有一个接口可以从一个节点移到它的兄弟节点、父节点和子节点,那么基于游标的迭代器是个更好的选择。游标只需跟踪当前的节点;它可依赖这种节点接口来遍历该符合对象。
符合常常需要用多种方法遍历。前序,后序,中序以及广度优先遍历都是常用的。你可用不同的迭代器类来支持不同的遍历。

空迭代器

一个空迭代器是一个退化的迭代器,它有助于处理边界条件。根据定义,一个NullIterator总是已经完成了遍历:即,它的IsDone操作总是返回true。
空迭代器使得更容易遍历树形结构的聚合(如复合对象)。在遍历过程中的每一个节点,都可向当前的元素请求遍历其各个子节点的迭代器。该聚合元素将返回一个具体的迭代器。但叶节点元素返回NullIterator的一个实例。这就使我们可以用一种统一的方式实现在整个结构上的遍历。

经典例子

Java 中的Collection

相关模式

Visitor Pattern

迭代模式是从聚合逐一取出元素并递增上去。虽然递增这个操作是为了对元素进行某种处理,但Iterator并没有实现该处理;访问者模式则是穿梭在多个元素的聚合内,不断重复同一处理。

Composite Pattern

组合模式是一种具有递归结构的模式,迭代模式适用于这个部分。

Factory Method Pattern

迭代模式在创建Iterator的对象实例时,有时会用到工厂方法模式。多态迭代器靠Factory Method来实例化适当的迭代器子类。

Memento

常与迭代器模式一起使用。迭代器可使用一个memento来捕获一个迭代的状态。迭代器在其内部存储memento。
敬请期待“调停者模式(Mediator Pattern,对象行为型模式,中介者模式)”

1 0
原创粉丝点击