5.4 ITERATOR(迭代器) — 对象行为型模式

1 意图

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

2 别名

游标(Curse)

3 动机

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

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

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


image.png

在实例化列表迭代器之前,必须提供遍历的列表。一旦有了该列表迭代器的实例,就可以顺序地访问该列表的各个元素,CurrentItem操作返回表列中的当前元素,First操作初始化迭代器,使当前元素指向列表的第一个元素 ,Next操作将当前元素指针向前推进一步,指向下一个元素, 而IsDone检查是否已越过最后一个元素,也就是完成了此次遍历。

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

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

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

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


image.png

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

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

4 适用性

迭代器模式可用来:

  • 1 访问一个聚合对象的内容而无需暴露它的内部表示;
  • 2 支持对聚合对象的多种遍历;
  • 3 为遍历不同的聚合结构提供一个统一的接口 (即, 支持多态迭代)
5 结构
image.png
6 参与者
  • Iterator(迭代器)
    ——迭代器定义访问和遍历元素的接口
  • concreteIterator(具体迭代器)
    ——具体迭代器实现迭代器接口
    —— 对该聚合遍历时跟踪当前位置
  • Aggregate(聚合)
    —— 聚合定义创建相应迭代器对象的接口
  • ConcreteAggregate(具体聚合)
    ——具体聚合实现创建相应迭代器的接口,该操作返回concreteIterator的一个适当的实例。
7 协作

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

8 效果

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

  • 1 它支持以不同的方式遍历一个聚合复杂的聚合可用多种方式进行遍历。例如 , 代码生成和语义检查要遍历语法分析树。代码生成可以按中序或者按前序来遍历语法分析树。迭代器模式使得改变遍历算法变得很容易 : 仅需用一个不同的迭代器的实例代替原先的实例即可。你也可以自己定义迭代器的子类以支持新的遍历;
  • 2 迭代器简化了聚合的接口有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了。这样就简化了聚合的接口。
  • 3 在同一个聚合上可以有多个遍历 每个迭代器保持它自己的遍历状态。因此你可以同时进行多个遍历。
9 实现

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

  • 1 谁控制该迭代 一个基本的问题是决定由哪一方来控制该迭代 , 是迭代器还是使用该迭代器的客户。当由客户来控制迭代时 , 该迭代器称为一个外部迭代器(external iterator),而当由迭代器控制迭代时, 该迭代器称为一个内部迭代器(internal iterator)。使用外部迭代器的客户必须主动推进遍历的步伐,显式地向迭代器请求下一个元素。相反地 , 若使用内部迭代器,客户只需向其提交一个待执行的操作,而迭代器将对聚合中的每一个元素实施该操作。
    外部迭代器比内部迭代器更灵活。例如 , 若要比较两个集合是否相等,这个功能很容易用外部迭代器实现,而几乎无法用内部迭代器实现。在象 C + +这样不提供匿名函数、闭包 , 或象Smalltalk和CLOS这样不提供连续( continuation)的语言中,内部迭代器的弱点更为明显。但另一方面, 内部迭代器的使用较为容易, 因为它们已经定义好了迭代逻辑。
  • 2 谁定义遍历算法 迭代器不是唯一可定义遍历算法的地方。聚合本身也可以定义遍历算法,并在遍历过程中用迭代器来存储当前迭代的状态。我们称这种迭代器为一个游标,因为它仅用来指示当前位置。客户会以这个游标为一个参数调用该聚合的Next操作,而Next操作将改变这个指示器的状态。
    如果迭代器负责遍历算法 , 那么将易于在相同的聚合上使用不同的迭代算法 , 同时也易于在不同的聚合上重用相同的算法。从另一方面说 , 遍历算法可能需要访问聚合的私有变量。如果这样,将遍历算法放入迭代器中会破坏聚合的封装性。
  • 3 迭代器健壮程度如何 在遍历一个聚合的同时更改这个聚合可能是危险的。如果在遍历聚合的时候增加或删除该聚合元素 , 可能会导致两次访问同一个元素或者遗漏掉某个元素。一个简单的解决办法是拷贝该聚合,并对该拷贝实施遍历 , 但一般来说这样做代价太高。
    一个健壮的迭代器(robust iterator)保证插入和删除操作不会干扰遍历, 且不需拷贝该聚合。有许多方法来实现健壮的迭代器。其中大多数需要向这个聚合注册该迭代器。当插入或删除元素时,该聚合要么调整迭代器的内部状态 , 要么在内部的维护额外的信息以保证正确的遍历。
  • 4 附加的迭代器操作迭代器的最小接口由First、Next、IsDone和CurrentItem 操作组成。其他一些操作可能也很有用。例如 , 对有序的聚合可用一个Previous操作将迭代器定位到前一个元素。SkipTo操作用于已排序并做了索引的聚合中,它将迭代器定位到符合指定条件的元素对象上。
  • 5 在C + +中使用多态的迭代器使用多态迭代器是有代价的。它们要求用一个Factory Method动态的分配迭代器对象。因此仅当必须多态时才使用它们。否则使用在栈中分配内存的具体的迭代器。
    多态迭代器有另一个缺点 : 客户必须负责删除它们。这容易导致错误 , 因为你容易忘记释放一个使用堆分配的迭代器对象,当一个操作有多个出口时尤其如此。而且其间如果有异常被触发的话,迭代器对象将永远不会被释放。
  • 6 迭代器可有特权访问迭代器可被看为创建它的聚合的一个扩展。迭代器和聚合紧密耦合。在C + +中我们可让迭代器作为它的聚合的一个友元 (friend )来表示这种紧密的关系。这样你就不需要在聚合类中定义一些仅为迭代器所使用的操作。
    但是, 这样的特权访问可能使定义新的遍历变得很难 , 因为它将要求改变该聚合的接口增加另一个友元。为避免这一问题 , 迭代器类可包含一些protected操作来访问聚合类的重要的非公共可见的成员。迭代器子类 (且只有迭代器子类)可使用这些protected操作来得到对该聚合的特权访问。
  • 7 用于复合对象的迭代器在Composite ( 4 . 3 )模式中的那些递归聚合结构上 , 外部迭代器可能难以实现, 因为在该结构中不同对象处于嵌套聚合的多个不同层次, 因此一个外部迭代器为跟踪当前的对象必须存储一条纵贯该 Composite的路径。有时使用一个内部迭代器会更容易一些。它仅需递归地调用自己即可,这样就隐式地将路径存储在调用栈中,而无需显式地维护当前对象位置。如果复合中的节点有一个接口可以从一个节点移到它的兄弟节点、父节点和子节点 , 那么基于游标的迭代器是个更好的选择。游标只需跟踪当前的节点 ; 它可依赖这种节点接口来遍历该复合对象。复合常常需要用多种方法遍历。前序 , 后序, 中序以及广度优先遍历都是常用的。你可用不同的迭代器类来支持不同的遍历。
  • 8 空迭代器一个空迭代器(NullIterator)是一个退化的迭代器 , 它有助于处理边界条件。根据定义,一个NullIterator总是已经完成了遍历:即, 它的IsDone操作总是返回true。空迭代器使得更容易遍历树形结构的聚合 (如复合对象)。在遍历过程中的每一节点 , 都可向当前的元素请求遍历其各个子结点的迭代器。该聚合元素将返回一个具体的迭代器。但叶节点元素返回NullIterator的一个实例。这就使我们可以用一种统一的方式实现在整个结构上的遍历。
9 代码示例

github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 工资表数据的整合## 考虑这样一个实际应用:整合工资表数据。 这个项目的背景是这样的,项目...
    七寸知架构阅读 2,424评论 0 53
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 迭代器模式Iterator 背景 概述 类中的面向对象编程封装应用逻辑。类,就是实例化的对象,每个单独的对象都有一...
    践行者阅读 504评论 1 3
  • 目录 本文的结构如下: 引言 什么是迭代器模式 模式的结构 典型代码 代码示例 优点和缺点 适用环境 模式应用 一...
    w1992wishes阅读 486评论 0 1
  • 手机屏幕坏了,哥哥帮我换了才算修好。 我还在时间的缝隙里游荡,努力挣扎。依旧是这个状态,跟我辞职的时候相比,已经八...
    蹒跚幸福阅读 212评论 0 0