1 迭代器模式概述

1.1 模式的提出

在程序设计中,经常要访问一个聚合对象中的各个元素,如链表、集合的遍历。通常的做法是将链表的创建和遍历都放在同一个类中。但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了面向对象设计原则中的开闭原则

将遍历方法封装在聚合类中是不可取的,但如果让用户自己实现遍历方法也不太好。首先这增加了用户的负担;其次这会暴露聚合类的内部表示,违背了面向对象编程的封装特性。

上面的两种方法都有不妥之处,为了解决这个问题,“迭代器模式”便应运而生了。

1.2 模式的定义与归类

1.2.1 模式的定义(意图)

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

1.2.2 模式的归类

迭代器模式属于对象行为型模式

对象行为型模式描述了一组对等的对象之间怎样相互协作共同完成其中任何一个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

2 迭代器模式的结构与实现

2.1 模式的结构

迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,这符合面向对象设计的单一职责原则。该设计模式的参与者分别是:

  1. Iterator(抽象迭代器)
    定义访问和遍历元素的接口,通常包含 first()next()isDone() 等方法。

  2. ConcreteIterator(具体迭代器)
    实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,并记录遍历的当前位置。

  3. Aggregate(抽象聚合)
    定义存储、添加、删除聚合对象以及创建相应迭代器对象的接口。

  4. ConcreteAggregate(具体聚合)
    实现创建相应迭代器的接口,返回一个具体迭代器的实例。

迭代器模式的结构如图 1 所示。

2.2 模式的实现

本小节以外部迭代器为例,具体阐述迭代器模式的实现方式。

2.2.1 抽象迭代器 Iterator

Iterator 类的实现如代码片段 1 所示。

【代码片段 1】抽象迭代器 Iterator 的实现

template<class Item>
class Iterator
{public:Iterator() {}virtual ~Iterator() {}virtual void first() = 0;virtual void next() = 0;virtual Item *currentItem() = 0;virtual bool isDone() = 0;
};

2.2.2 具体迭代器 ConcreteIterator

ConcreteIterator 类的实现如代码片段 2 所示。

【代码片段 2】具体迭代器 ConcreteIterator 的实现

template<class Item>
class ConcreteIterator: public Iterator<Item>
{private:Aggregate<Item> *aggregate;int currentIndex;public:ConcreteIterator(Aggregate<Item> *aggregate): aggregate(aggregate), currentIndex(0) {}virtual ~ConcreteIterator() {}virtual void first(){currentIndex = 0;}virtual void next(){if(currentIndex < aggregate->size()){++currentIndex;}}virtual Item *currentItem(){if(!isDone()){return (aggregate->at(currentIndex));}else{throw "ConcreteIterator out of range!";}}virtual bool isDone(){return currentIndex >= aggregate->size();}
};

ConcreteIterator 实现了 Iterator 中定义的 4 个方法:

  1. first() 将迭代器指向聚合中的第一个元素。
  2. next() 使迭代器向前推进一步。
  3. isDone() 检查指向当前元素的索引是否超出了范围。
  4. currentItem() 返回当前索引指向的元素。如果迭代已经终止,则抛出一个异常。

2.2.3 抽象聚合 Aggregate

Aggregate 类的实现如代码片段 3 所示。

【代码片段 3】抽象聚合 Aggregate 的实现

template<class Item>
class Aggregate
{public:Aggregate() {}virtual ~Aggregate() {}virtual size_t size() = 0;virtual void append(Item item) = 0;virtual Item *at(const int index) = 0;virtual Iterator<Item> *createIterator() = 0;
};

2.2.4 具体聚合 ConcreteAggregate

ConcreteAggregate 类的实现如代码片段 4 所示。

【代码片段 4】具体聚合 ConcreteAggregate 的实现

template <class Item>
class ConcreteAggregate: public Aggregate<Item>
{private:std::vector<Item> data;public:ConcreteAggregate() {}virtual ~ConcreteAggregate() {}virtual size_t size(){return data.size();}virtual void append(Item item){data.push_back(item);}virtual Item *at(const int index){return &data[index];}virtual Iterator<Item> *createIterator(){return new ConcreteIterator<Item>(this);}
};

ConcreteAggregate 实现了一个线性表,其底层基于 STL 中的 vector,包含如下方法:

  1. size() 返回线性表中对象的数目。
  2. append(item) 在线性表尾部添加元素 item
  3. at(index) 返回指定下标 index 处的对象,但并不检查指定的下标是否合法。
  4. createIterator() 返回可以遍历本线性表的迭代器。

2.2.5 用户端

用户端的代码如代码片段 5 所示。

【代码片段 5】用户端的代码

int main(int argc, char *argv[])
{Aggregate<int> *list = new ConcreteAggregate<int>;for(int i = 1; i <= 10; i++){list->append(i);}Iterator<int> *iter = list->createIterator();for(iter->first(); !iter->isDone(); iter->next()){printf("%d ", *(iter->currentItem()));}printf("\n");try{iter->next();printf("%d", *(iter->currentItem()));}catch(const char *errorMessage){puts(errorMessage);}return 0;
}

程序运行的结果如图 2 所示。在该程序中首先定义了一个线性表,并向其中添加数字 1 到 10。然后用 createIterator() 方法获取迭代器,从头迭代至尾并输出元素的值(见输出第 1 行)。

在迭代已经终止后,程序试图继续往下迭代,最终在 catch 语句块中捕获到了迭代器抛出的异常,并输出提示信息(见输出第 2 行)。

3 迭代器模式的优缺点

3.1 优点

迭代器模式的主要优点如下:

  1. 访问一个聚合对象的内容,而无须暴露它的内部表示。
  2. 遍历任务交由迭代器完成,这简化了聚合类。
  3. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历方式。
  4. 增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  5. 封装性良好,为遍历不同的聚合结构提供一个统一的接口,从而支持同样的算法在不同的聚合结构上进行操作。

3.2 缺点

  1. 增加了系统的复杂性

由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

  1. 遍历时不能增删元素

在遍历的过程中,更改迭代器所在的聚合结构可能导致出现异常。所以使用迭代器只能对集合进行遍历,不能在遍历的同时增加或删除集合中的元素。
对该缺点的详细分析见 3.3 节。

3.3 对缺点的深入讨论

3.3.1 问题的提出

在通过迭代器来遍历集合元素的同时,增加或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。不过,并不是所有情况下都会遍历出错,有的时候也可以正常遍历。

以删除元素为例,在代码片段 5 中,当迭代器遍历到 6 的时候,如果从线性表中将迭代器前面的元素删除掉(例如元素 5),6 到 10 这几个元素会依次往前移一位,这就会导致迭代器本来指向元素 6,现在变成了指向元素 7。迭代器继续向下遍历时只能遍历到元素 8、9、10,元素 7 遍历不到了。

这一过程的示意图如图 3 所示。

不过,如果在遍历时删除的不是迭代器前面的元素(元素 1 到 5)以及迭代器所在位置的元素(元素 6),而是迭代器后面的元素(元素 7 到 10),这样就不会存在某个元素遍历不到的情况了。

这一过程的示意图如图 4 所示。

以上讨论了删除元素的情形。增加元素的情形与之类似,也可能存在某一元素被重复遍历的错误情况,示意图如图 5 所示。

3.3.2 问题的解决

当通过迭代器来遍历聚合对象的时候,增加、删除元素会导致不可预期的遍历结果。为了避免出现这种情况,有两种可能的解决方案:

  1. 遍历的时候不允许增删元素。
  2. 增加或删除元素之后抛出异常。

实际上,第一种解决方法比较难实现,而第二种解决方法更加合理。具体原因请阅读参考文献[5]。

4 总结

迭代器模式是一种对象行为型模式,它提供了一种在不暴露底层表示的情况下,透明地访问和遍历一个集合中的对象的方式。迭代器模式普遍的应用于访问列表、集合、字典等数据类型。

虽然对于 C++、Java、Python 等现代编程语言,迭代器模式已经内置到语言本身或者类库中了,并不需要我们自己去编写相关的实现代码;但是学习迭代器模式的基本结构,掌握迭代器模式的思想,对我们理解面向对象设计的原则有很大帮助。


【参考文献】

[1] 埃里克·伽玛, 理查德·赫尔姆, 拉尔夫·约翰逊等. 设计模式:可复用面向对象软件的基础[M]. 李英军等译. 北京:机械工业出版社, 2019.

[2] 迭代器模式(详解版)[EB/OL]. C语言中文网, [2020-11-28]. http://c.biancheng.net/view/1395.html.

[3] 董威, 文艳军, 齐治昌. 软件设计与体系结构[M]. 第2版. 北京:高等教育出版社, 2017.

[4] Bing_Lee. 简单易懂23种设计模式——迭代器模式[EB/OL]. CSDN博客, (2020-09-15) [2020-11-28]. https://blog.csdn.net/Bing_Lee/article/details/108608431.

[5] 王争. 迭代器模式(中):遍历集合的同时,为什么不能增删集合元素?[EB/OL]. 极客时间, (2020-04-03) [2020-11-28].
https://time.geekbang.org/column/article/219964.


本文系《软件设计与体系结构》课程作业。

设计模式分析——迭代器模式(Iterator)相关推荐

  1. 设计模式 - 迭代器模式(iterator pattern) 具体解释

    迭代器模式(iterator pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 迭代器模式(iterator pattern) : 提供一 ...

  2. 设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型)

    设计模式 ( 十四 ) 迭代器模式Iterator(对象行为型) 1.概述 类中的面向对象编程封装应用逻辑.类,就是实例化的对象,每个单独的对象都有一个特定的身份和状态.单独的对象是一种组织代码的有用 ...

  3. 设计模式:迭代器模式(Iterator Pattern)

    迭代器模式(Iterator Pattern): 属于行为型模式,提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即: 不暴露其内部结构.

  4. c++迭代器模式iterator

    c++迭代器模式iterator 概念 角色和职责 案例 概念 Iterator模式也叫迭代模式,是行为模式之一,它把对容器中包含的内部对象的访问委让给外部类,使用Iterator(遍历)按顺序进行遍 ...

  5. 迭代器模式(Iterator pattern)

    一. 引言 迭代这个名词对于熟悉Java的人来说绝对不陌生.我们常常使用JDK提供的迭代接口进行java collection的遍历: Iterator it = list.iterator(); w ...

  6. 设计模式之迭代器模式(Iterator)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  7. 迭代器模式(Iterator)解析例子

    摘要:本文深入浅出的讲述了设计模式中的迭代器模式,并给出了简单的示例,例子浅显易懂,并附带源代码. 迭代器模式属于行为型模式,其意图是提供一种方法顺序访问一个聚合对象中得各个元素,而又不需要暴露该对象 ...

  8. 解读设计模式----迭代器模式(Iterator Pattern),谁才是迭代高手

    一.你在开发中使用过迭代吗?      当你在使用JavaScript开发客户端应用的时候使用过for...in吗?  1<script type="text/javascript&q ...

  9. 听webcast的行为型模式篇-迭代器模式(Iterator Pattern) 记录

    < DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd> dotnet或java里 ...

最新文章

  1. NHibernate和Cuyahoga(二)(翻译):
  2. 全球充电最快手机:5分钟回血50%;华为未发布新手机 | MWC 2022
  3. Web框架 — Flask
  4. HTML 文件在PC移动端完美自适应布局的技巧
  5. JavaScript中的数组和字符串
  6. fiddler对手机进行抓包
  7. apache 配置文件内使用 8080 端口_【SpringBoot 框架】- SpringBoot 配置文件
  8. 可编程ic卡 通用吗_8255可编程IC
  9. Oracle 摘去数据块的面纱
  10. c语言stl大全,C++ STL库应用汇总
  11. 稳定触发windows蓝屏的路径漏洞,不要用来整人!
  12. Mysql中从零点到当前时刻的SQL判断
  13. JAVA Swing界面美化 -付费界面库
  14. 面对满天繁星似的知识.如何做到老虎吃天
  15. 联想电脑G40无法使用 非要睡眠后才能启用wifi
  16. 炫云全新支持优化渲染质量了
  17. 关于2021年11月28日PMI认证考试的报名通知
  18. python3识别简单验证码
  19. Java用栈实现排序_Java中的栈排序
  20. 安装MinGW-w64

热门文章

  1. 胡阳pyhton作业题--20150725
  2. vue设置页面背景色
  3. uni-app引入阿里图标
  4. 【密码资料】纳瓦霍密码
  5. 浅谈深度学习:基于对LSTM项目`LSTM Neural Network for Time Series Prediction`的理解与回顾
  6. 百万数据使用子查询进行SQL优化
  7. PS制作android图标
  8. Linux配置sendmail实现PHP发送邮件
  9. SQL server不能用IP登录
  10. esp32开发快速入门 8 : MQTT 的快速入门,基于esp32实现MQTT通信