3. 行为模式 - 迭代器模式


亦称: Iterator

 意图

迭代器模式是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。

迭代器设计模式

 问题

集合是编程中最常使用的数据类型之一。 尽管如此, 集合只是一组对象的容器而已。

各种类型的集合

各种类型的集合。

大部分集合使用简单列表存储元素。 但有些集合还会使用栈、 树、 图和其他复杂的数据结构。

无论集合的构成方式如何, 它都必须提供某种访问元素的方式, 便于其他代码使用其中的元素。 集合应提供一种能够遍历元素的方式, 且保证它不会周而复始地访问同一个元素。

如果你的集合基于列表, 那么这项工作听上去仿佛很简单。 但如何遍历复杂数据结构 (例如树) 中的元素呢? 例如, 今天你需要使用深度优先算法来遍历树结构, 明天可能会需要广度优先算法; 下周则可能会需要其他方式 (比如随机存取树中的元素)。

各种遍历算法

可通过不同的方式遍历相同的集合。

不断向集合中添加遍历算法会模糊其 “高效存储数据” 的主要职责。 此外, 有些算法可能是根据特定应用订制的, 将其加入泛型集合类中会显得非常奇怪。

另一方面, 使用多种集合的客户端代码可能并不关心存储数据的方式。 不过由于集合提供不同的元素访问方式, 你的代码将不得不与特定集合类进行耦合。

 解决方案

迭代器模式的主要思想是将集合的遍历行为抽取为单独的迭代器对象。

迭代器可以实现不同算法

迭代器可实现多种遍历算法。 多个迭代器对象可同时遍历同一个集合。

除实现自身算法外, 迭代器还封装了遍历操作的所有细节, 例如当前位置和末尾剩余元素的数量。 因此, 多个迭代器可以在相互独立的情况下同时访问集合。

迭代器通常会提供一个获取集合元素的基本方法。 客户端可不断调用该方法直至它不返回任何内容, 这意味着迭代器已经遍历了所有元素。

所有迭代器必须实现相同的接口。 这样一来, 只要有合适的迭代器, 客户端代码就能兼容任何类型的集合或遍历算法。 如果你需要采用特殊方式来遍历集合, 只需创建一个新的迭代器类即可, 无需对集合或客户端进行修改。

 真实世界类比

漫步罗马的不同方式

漫步罗马的不同方式。

你计划在罗马游览数天, 参观所有主要的旅游景点。 但在到达目的地后, 你可能会浪费很多时间绕圈子, 甚至找不到罗马斗兽场在哪里。

或者你可以购买一款智能手机上的虚拟导游程序。 这款程序非常智能而且价格不贵, 你想在景点待多久都可以。

第三种选择是用部分旅行预算雇佣一位对城市了如指掌的当地向导。 向导能根据你的喜好来安排行程, 为你介绍每个景点并讲述许多激动人心的故事。 这样的旅行可能会更有趣, 但所需费用也会更高。

所有这些选择(自由漫步、 智能手机导航或真人向导)都是这个由众多罗马景点组成的集合的迭代器。

 迭代器模式结构

迭代器设计模式的结构

  1. 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。

  2. 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。

  3. 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。

  4. 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。

  5. 客户端 (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。

    客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。

 伪代码

在本例中, 迭代器模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。

迭代器模式示例的结构

遍历社交档案的示例

“好友 (friends)” 迭代器可用于遍历指定档案的好友。 ​ “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。

客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。

// 集合接口必须声明一个用于生成迭代器的工厂方法。如果程序中有不同类型的迭
// 代器,你也可以声明多个方法。
interface SocialNetwork ismethod createFriendsIterator(profileId):ProfileIteratormethod createCoworkersIterator(profileId):ProfileIterator// 每个具体集合都与其返回的一组具体迭代器相耦合。但客户并不是这样的,因为
// 这些方法的签名将会返回迭代器接口。
class WeChat implements SocialNetwork is// ……大量的集合代码应该放在这里……// 迭代器创建代码。method createFriendsIterator(profileId) isreturn new WeChatIterator(this, profileId, "friends")method createCoworkersIterator(profileId) isreturn new WeChatIterator(this, profileId, "coworkers")// 所有迭代器的通用接口。
interface ProfileIterator ismethod getNext():Profilemethod hasMore():bool// 具体迭代器类。
class WeChatIterator implements ProfileIterator is// 迭代器需要一个指向其遍历集合的引用。private field weChat: WeChatprivate field profileId, type: string// 迭代器对象会独立于其他迭代器来对集合进行遍历。因此它必须保存迭代器// 的状态。private field currentPositionprivate field cache: array of Profileconstructor WeChatIterator(weChat, profileId, type) isthis.weChat = weChatthis.profileId = profileIdthis.type = typeprivate method lazyInit() isif (cache == null)cache = weChat.socialGraphRequest(profileId, type)// 每个具体迭代器类都会自行实现通用迭代器接口。method getNext() isif (hasMore())result = cache[currentPosition]currentPosition++return resultmethod hasMore() islazyInit()return currentPosition < cache.length// 这里还有一个有用的绝招:你可将迭代器传递给客户端类,无需让其拥有访问整
// 个集合的权限。这样一来,你就无需将集合暴露给客户端了。
//
// 还有另一个好处:你可在运行时将不同的迭代器传递给客户端,从而改变客户端
// 与集合互动的方式。这一方法可行的原因是客户端代码并没有和具体迭代器类相
// 耦合。
class SocialSpammer ismethod send(iterator: ProfileIterator, message: string) iswhile (iterator.hasMore())profile = iterator.getNext()System.sendEmail(profile.getEmail(), message)// 应用程序(Application)类可对集合和迭代器进行配置,然后将其传递给客户
// 端代码。
class Application isfield network: SocialNetworkfield spammer: SocialSpammermethod config() isif working with WeChatthis.network = new WeChat()if working with LinkedInthis.network = new LinkedIn()this.spammer = new SocialSpammer()method sendSpamToFriends(profile) isiterator = network.createFriendsIterator(profile.getId())spammer.send(iterator, "非常重要的消息")method sendSpamToCoworkers(profile) isiterator = network.createCoworkersIterator(profile.getId())spammer.send(iterator, "非常重要的消息")

 迭代器模式适合应用场景

 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。

 迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。

 使用该模式可以减少程序中重复的遍历代码。

 重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。

 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

 该模式为集合和迭代器提供了一些通用接口。 如果你在代码中使用了这些接口, 那么将其他实现了这些接口的集合和迭代器传递给它时, 它仍将可以正常运行。

 实现方式

  1. 声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素。 但为了使用方便, 你还可以添加一些其他方法, 例如获取前一个元素、 记录当前位置和判断迭代是否已结束。

  2. 声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口。 如果你计划拥有多组不同的迭代器, 则可以声明多个类似的方法。

  3. 为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立。

  4. 在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。

  5. 检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器。

 迭代器模式优缺点

  •  单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
  •  开闭原则。 你可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
  •  你可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
  •  相似的, 你可以暂停遍历并在需要时继续。
  •  如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
  •  对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。

 与其他模式的关系

  • 你可以使用迭代器模式来遍历组合模式树。

  • 你可以同时使用工厂方法模式和迭代器来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。

  • 你可以同时使用备忘录模式和迭代器来获取当前迭代器的状态, 并且在需要的时候进行回滚。

  • 可以同时使用访问者模式和迭代器来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。

 代码示例

/*** Iterator Design Pattern** Intent: Lets you traverse elements of a collection without exposing its* underlying representation (list, stack, tree, etc.).*/#include <iostream>
#include <string>
#include <vector>/*** C++ has its own implementation of iterator that works with a different* generics containers defined by the standard library.*/template <typename T, typename U>
class Iterator {public:typedef typename std::vector<T>::iterator iter_type;Iterator(U *p_data, bool reverse = false) : m_p_data_(p_data) {m_it_ = m_p_data_->m_data_.begin();}void First() {m_it_ = m_p_data_->m_data_.begin();}void Next() {m_it_++;}bool IsDone() {return (m_it_ == m_p_data_->m_data_.end());}iter_type Current() {return m_it_;}private:U *m_p_data_;iter_type m_it_;
};/*** Generic Collections/Containers provides one or several methods for retrieving* fresh iterator instances, compatible with the collection class.*/template <class T>
class Container {friend class Iterator<T, Container>;public:void Add(T a) {m_data_.push_back(a);}Iterator<T, Container> *CreateIterator() {return new Iterator<T, Container>(this);}private:std::vector<T> m_data_;
};class Data {public:Data(int a = 0) : m_data_(a) {}void set_data(int a) {m_data_ = a;}int data() {return m_data_;}private:int m_data_;
};/*** The client code may or may not know about the Concrete Iterator or Collection* classes, for this implementation the container is generic so you can used* with an int or with a custom class.*/
void ClientCode() {std::cout << "________________Iterator with int______________________________________" << std::endl;Container<int> cont;for (int i = 0; i < 10; i++) {cont.Add(i);}Iterator<int, Container<int>> *it = cont.CreateIterator();for (it->First(); !it->IsDone(); it->Next()) {std::cout << *it->Current() << std::endl;}Container<Data> cont2;Data a(100), b(1000), c(10000);cont2.Add(a);cont2.Add(b);cont2.Add(c);std::cout << "________________Iterator with custom Class______________________________" << std::endl;Iterator<Data, Container<Data>> *it2 = cont2.CreateIterator();for (it2->First(); !it2->IsDone(); it2->Next()) {std::cout << it2->Current()->data() << std::endl;}delete it;delete it2;
}int main() {ClientCode();return 0;
}
执行结果
________________Iterator with int______________________________________
0
1
2
3
4
5
6
7
8
9
________________Iterator with custom Class______________________________
100
1000
10000

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/238837.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构(八):图介绍及面试常考算法

一、图介绍 1、定义 图由结点的有穷集合V和边的集合E组成。其中&#xff0c;结点也称为顶点。一对结点&#xff08;x&#xff0c; y&#xff09;称为边&#xff08;edge&#xff09;&#xff0c;表示顶点x连接到顶点y。边可以包含权重/成本&#xff0c;显示从顶点x到y所需的成…

深入解析Python装饰器及*args, **kwargs的妙用

深入解析Python装饰器及*args, **kwargs的妙用 简介&#xff1a; ​ 装饰器&#xff08;Decorator&#xff09;是 Python 中一种强大的语法特性&#xff0c;它允许在不修改原始函数代码的情况下&#xff0c;动态地扩展函数的功能。装饰器是函数或类&#xff0c;用于包装其他函…

云上荆楚丨云轴科技ZStack成功实践精选(湖北)

湖北自古以来有九省通衢的美称&#xff0c;地处长江中游&#xff0c;富有荆楚之美誉&#xff0c;灵秀之蕴意。2022年湖北数字经济强省三年行动计划正式印发&#xff0c;计划到“十四五”末&#xff0c;数字经济核心产业增加值力争达到7000亿元&#xff0c;占GDP的比重超过12%。…

《每天一分钟学习C语言·七》指针、字节对齐等

1、 对于二维数组如a[3][4]可以当做有三个元素的一维数组&#xff0c;每个元素包含四个小元素。 2、 printf(“%-5d”, i); //负号表示左对齐&#xff0c;5d表示空五个光标的位置 3、 栈&#xff1a;先进后出&#xff0c;堆&#xff1a;先进先出 4、 &#xff08;1&#xff…

位运算:Leetcode137.只出现一次的数字(2)

题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 示例 1&#xff1a; 输入&#xff1a;nums [2,2,3,2] 输出&#xff1a;3示例 2&#xff1a; 输入&…

《工具箱-SVN》SVN安装、备份、迁移教程

文章目录 一、服务器搭建SVN1.检查SVN是否存在2.安装SVN3.创建版本库4.创建版本库存放文件地址5.修改配置文件5.1 vim authz5.2 vim passwd5.3 vim svnserve.conf 6.启动并查看SVN7.SVN Checkout8.SVN Update9.SVN Commit 二、SVN-无法连接主机&#xff0c;目标计算机积极拒绝&…

案例 | 电源自动检测测试系统为某电子科技公司定制电源测试解决方案

一、测试背景 陕西某电子科技公司是一家专业生产设计军品电源、集成电路以及电子元器件的高新技术企业&#xff0c;公司虽有一套半自动ATE测试系统&#xff0c;但使用过程繁琐复杂且无法满足日益增长的测试需求&#xff0c;因此公司现需要一套更加优秀的全自动电源测试系统来应…

工具系列:PyCaret介绍_多分类代码示例

&#x1f44b; 工具系列&#xff1a;PyCaret介绍_多分类代码示例 PyCaret 介绍 PyCaret是一个开源的、低代码的Python机器学习库&#xff0c;可以自动化机器学习工作流程。它是一个端到端的机器学习和模型管理工具&#xff0c;可以大大加快实验周期并提高生产效率。 与其他开…

HDFS NFS Gateway(环境配置,超级详细!!)

&#x1f42e;博主syst1m 带你 acquire knowledge&#xff01; ✨博客首页——syst1m的博客&#x1f498; &#x1f618;《CTF专栏》超级详细的解析&#xff0c;宝宝级教学让你从蹒跚学步到健步如飞&#x1f648; &#x1f60e;《大数据专栏》大数据从0到秃头&#x1f47d;&…

【论文阅读】FreeU: Free Lunch in Diffusion U-Net

FreeU: 无需训练直接提升扩散模型生成效果。 paper&#xff1a;https://arxiv.org/abs/2309.11497 code&#xff1a;GitHub - ChenyangSi/FreeU: FreeU: Free Lunch in Diffusion U-Net 1. 介绍 贡献&#xff1a; •研究并揭示了U-Net架构在扩散模型中去噪的潜力&#xff0…

Redis单机、主从、哨兵、集群配置

单机配置启动 Redis安装 下载地址&#xff1a;Download | Redis 安装步骤&#xff1a; 1: 安装gcc编译器&#xff1a;yum install gcc 2: 将下载好的redis‐5.0.3.tar.gz文件放置在/usr/local文件夹下&#xff0c;并解压redis‐5.0.3.tar.gz文件 wget http://download.re…

react生命周期详解,代码示例(新生命周期,与旧生命周期对比)

旧生命周期&#xff1a;https://blog.csdn.net/kkkys_kkk/article/details/135130549?spm1001.2014.3001.5501 目录 React 生命周期中常见的坑 为什么要移除 “will” 相关生命周期方法呢&#xff1f; Fiber是什么 新生命周期图示 新增生命周期与功能变化 完整生命周期…

C语言操作符详解+运算符优先级表格

目录 前言 一、操作符是什么&#xff1f; 二、操作符的分类 三、算术操作符 四、逻辑操作符 五、比较操作符 六、位操作符 七、赋值操作符 八、其他操作符 九、运算符优先级表格 总结 前言 在编写程序时&#xff0c;最常用到的就是操作符&#xff0c;本文将详细的介绍…

Golang 的内存管理

文章目录 1.内存管理角色1.常见的内存分配方法线性分配器空闲链表分配器TCMalloc 2.Go 内存管理组件mspanmcache初始化替换微分配器 mcentralmheap 3.内存分配4.内存管理思想参考文献 1.内存管理角色 内存管理一般包含三个不同的组件&#xff0c;分别是用户程序&#xff08;Mu…

【C语言】指针详解(三)

1.指针运算 指针的基本运算有三种&#xff0c;分别是:⭐指针-整数 ⭐指针-指针 ⭐指针的关系运算 1.1指针 - 整数 因为数组在内存中是连续存放的&#xff0c;只要知道第一个元素的地址&#xff0c;顺藤摸瓜就能找到后面的所有元素。 int arr[10]{1,2,3,4,5,6,7,8,9,10} #inc…

劈窗算法反演地表温度

目录 摘要操作步骤提取热红外单波段提取NDVI同步像元分辨率与个数劈窗算法地表温度反演制图 摘要 主要使用HJ-2&#xff08;环境减灾二号卫星&#xff09;的IRS传感器的两个热红外波段&#xff0c;以及红波段与近红波段计算得到的NDVI&#xff0c;使用劈窗算法&#xff0c;得到…

贪吃蛇(五)蛇撞墙

上节我们实现了蛇身向右移动的功能&#xff0c;原理就是增加一个节点&#xff0c;删除一个节点。 本节我们处理蛇撞墙重置的功能 实现原理 在移动函数中检查蛇头&#xff08;链表尾节点&#xff09;是否达到墙边的坐标&#xff0c;这里有四种撞墙的情况&#xff1a; 上墙&am…

使用Docker-镜像命令

镜像名称一般分两部分组成:[repository]:[tag] 在没有指定tag时&#xff0c;默认是latest&#xff0c;代表最新版本的镜像 案例一&#xff1a;从DockerHub中拉取一个nginx镜像并查看 1.1. 首先去镜像仓库搜索nginx镜像&#xff0c;比如DockerHub 点击nginx 复制拉取命令 1.2.…

MySQL中替换字符串中的指定部分之REPLACE函数

REPLACE函数是用来替换字符串中的指定部分内容的。在本文中&#xff0c;将介绍如何在MySQL中使用REPLACE函数进行字符串替换 REPLACE函数的语法&#xff1a; REPLACE(str, search_str, replace_str) 其中&#xff0c;str是要进行替换操作的字符串&#xff0c;search_str是要搜…

使用Mosquitto/python3进行MQTT连接

一、简介 MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上&#xff0c;是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议&#xff0c;为此&#xff0c;它需要一个消息中间件。 …