设计模式——2_3 迭代器(Iterator)

生活就像一颗巧克力,你永远不知道下一颗是什么味道

——《阿甘正传》

文章目录

  • 定义
  • 图纸
  • 一个例子:假如你的供应商提供了不同类型的返回值
    • 单独的遍历流程
      • 实现
  • 碎碎念
    • 如果读写同时进行会发生啥?
    • 外部迭代和内部迭代
    • 迭代器和其他模式
      • 迭代器和组合
      • 迭代器和状态

定义

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

迭代器模式,又称为游标模式。所以迭代器和游标本质上是一回事,虽然在各个语言中的具体实现和命名会有差异

比如Java里面的容器类有的那个东西叫迭代器、但是操作数据库时的那个ResultSet我们又将他称之为游标,但本质上他们是一回事

迭代器在Java和.net环境中的应用随处可见,两者的默认容器的实现中都使用的迭代器模式,而且他们的foreach遍历,本质上都是对迭代器遍历写法的简化



图纸

在这里插入图片描述



一个例子:假如你的供应商提供了不同类型的返回值

这个名字好,不去写本日系轻小说都可惜了,恨不得一个标题就把内容全剧透完

虽然这个标题又俗又长,但是这种情况其实非常常见。至少在鄙人目前还不算长的职业生涯里,几乎每次换设备供应商都会有 这种情况的出现。因为这玩意也没个标准,一些老式设备返还的甚至都不是json。所以这个例子算抛砖引玉,但我由衷地希望 各位道友不需要考虑这样的问题

准备好了吗?这次的例子开始了:


假定你的公司升级考勤机,从原来的磁卡考勤机,换成人脸考勤机。但是磁卡考勤机不会直接报废,因为某些特殊位置依然可以使用他。这样一来,你每次读取数据的时候就要同时读取两种考勤机上的数据。

接着,我们根据两种考勤机的API,构造出这样一个结构:

在这里插入图片描述

我们定义了一个 DataReader(数据读取器) 接口用于定义可以从考勤机硬件上获取到的信息(比如考勤机号、ip地址等数据),在里面我们通过 read() 方法读取考勤机上的数据,并把读取到的数据以返回值的形式返回给 client代码

但是在写 DataReader 的具体实现的时候,出现了一个很严重的问题,我们发现A类型的考勤机API和B类型的考勤机API,在读取考勤机上的数据时,提供了完全不同的返回值

为了简化所以使用了 String 类型作为返回值,但是实际情况考勤机返回的一定是各家考勤机自己定义数据格式的原始数据。这时候一般做法是:定义一个包含所需信息的数据bean,并在 read() 方法中把读取到的原始数据转换成你自己定义的bean对象


这就很尴尬了,不同的集合类型,我们没办法用相同的方式去遍历他们,这就意味着在操作A考勤机的返回值时我们要这样写:

for(int i = 0;i<list.size();i++){String element = list.get(i);//对element做的操作
}

而当考勤机是B时,我们要这样写:

for(int i = 0;i<array.lenth;i++){String element = array[i];//对element做的操作
}

现在我们的需求是:无论从哪里读取上来的数据,对element做的操作 这部分是一致的,不一致的是对容器对象的遍历方式

也就是说,我们需要一个方案,可以把对容器对象的遍历过程进行解耦


单独的遍历流程

迭代器模式 这时候是你的最优选,具体的做法是这样的:

在这里插入图片描述

实现

/*** 数据读取器*/
public interface DataReader {/*** 从考勤机上读取记录和各类信息*/void read();/*** 创建对应的迭代器*/Iterator<String> createIterator();
}/*** 迭代器*/
public interface Iterator<E> {/*** 还有下一个吗?** @return true:有*/boolean hasNext();/*** 下一条记录*/E next();
}/*** A类型的数据读取器*/
public class ADataReader implements DataReader {//从考勤机里读取上来的记录private List<String> data;@Overridepublic void read() {System.out.println("A类型考勤机正在从考勤机里读取数据。。。");data = new ArrayList<>();for (int i = 0; i < 5; i++) {data.add(i + "");}System.out.println("A类型考勤机读取完毕");}@Overridepublic Iterator<String> createIterator() {if (data == null) {throw new RuntimeException("还没有执行对考勤机的读取");} else {return new AIterator();}}class AIterator implements Iterator<String> {private int cursor = 0;@Overridepublic boolean hasNext() {return cursor < data.size();}@Overridepublic String next() {return data.get(cursor++);}}
}/*** B类型的数据读取器*/
public class BDataReader implements DataReader {private String[] data;@Overridepublic void read() {System.out.println("B类型考勤机正在从考勤机里读取数据。。。");data = new String[5];for (int i = 0; i < 5; ) {data[i] = (char) (++i + 96) + "";}System.out.println("B类型考勤机读取完毕");}@Overridepublic Iterator<String> createIterator() {if (data == null) {throw new RuntimeException("还没有执行对考勤机的读取");} else {return new BIterator();}}class BIterator implements Iterator<String> {private int cursor = 0;@Overridepublic boolean hasNext() {return cursor < data.length;}@Overridepublic String next() {return data[cursor++];}}
}

使用迭代器模式后,我们把 read 方法的职能进一步细化了,这个方法从此只负责从硬件上读取对应的数据到内存中,并存储在对应的 DataReader 内的 data 属性中

接着我们通过定义 createIterator 方法,对不同 DataReader 中不同 data 类型的遍历过程进行了解耦。这些变化被封装到我们定义的 Iterator 迭代器类树中(为了方便迭代器对底层容器的访问,我们还把两个AB两个迭代器写成了ABDataReader的子类)

至此 client代码 在遍历从考勤机中读取到的数据时不再关心底层的数据到底是以何种结构聚合在一起,而我们的程序将来如果要面对新的容器类型时,也只是新增一个 Iterator 子类的工作量而已


而这正是一个标准的迭代器实现



碎碎念

如果读写同时进行会发生啥?

但上面的实现存在一个问题:如果 client代码 调用迭代器进行迭代到一半,我又调用了一次 read 刷新了被迭代的容器。这时候迭代器的状态就失效了,可能会出现迭代越界,又或者重复迭代/漏迭代的问题

这是很危险的,所以一个健壮的迭代器应该可以保证即便在迭代过程中对容器进行修改也不会出现这么危险的行为


如果你在Java或者C#中对被迭代的容器进行修改,那么当你调用下一个next方法时会得到一个异常。为了实现这样的效果,一种常用的手法就是在迭代器被创建出来的时候向被遍历的容器注册自身,而容器在修改自身的时候可以向迭代器发出通知,就像这样(以A为例):

/*** A类型的数据读取器*/
public class ADataReader implements DataReader {//从考勤机里读取上来的记录private List<String> data; //进行注册的迭代器列表private final Set<AIterator> iteratorSet = new HashSet<>();@Overridepublic void read() {//把所有当前注册的迭代器都置为无效for (AIterator aIterator : iteratorSet) {aIterator.isUsable = false;//无效化}iteratorSet.clear();//清空注册列表,以便资源回收System.out.println("A类型考勤机正在从考勤机里读取数据。。。");data = new ArrayList<>();for (int i = 0; i < 5; i++) {data.add(i + "");}System.out.println("A类型考勤机读取完毕");}@Overridepublic Iterator<String> createIterator() {if (data == null) {throw new RuntimeException("还没有执行对考勤机的读取");} else {AIterator iterator = new AIterator();iteratorSet.add(iterator);//注册迭代器return iterator;}}class AIterator implements Iterator<String> {private boolean isUsable = true;private int cursor = 0;@Overridepublic boolean hasNext() {if (isUsable) {return cursor < data.size();} else {throw new RuntimeException("此迭代器已失效");}}@Overridepublic String next() {if (isUsable) {return data.get(cursor++);} else {throw new RuntimeException("此迭代器已失效");}}}
}

在改良后的代码里,我们为 AIterator 引入了 isUsable(是否可用) 的概念。只有当这个值为true的时候,迭代器里的方法才能正常调用,否则会抛出一个异常

然后我们在 ADataReader 中新增了一个 iteratorSet 用于记录当前生效的这一批迭代器

接着,当我们每次调用 read 试图更新 data 的值的时候都会把 iteratorSet 里注册的所有迭代器的可用性变为false

这就实现了我们对迭代器的保护


外部迭代和内部迭代

在上例中,我们封装了不同类型的容器的遍历过程,以实现 client代码 可以随时随地用自己喜欢的方式遍历 DataReader 中的内容

事实上还有一种解决方案可以在不使用 Iterator 的情况下实现和上例同样的效果,而且可以不让 client代码 随意遍历 DataReader 里的内容,就像这样(以B为例):


/*** 数据操作手柄*/
public interface DataHandler<E> {/*** 具体的对数据的操作方法*/void handle(E e);
}/*** B类型的数据读取器*/
public class BDataReader {public void read(DataHandler<String> dataHandler) {String[] data = new String[5];for (int i = 0; i < data.length; ) {data[i] = (char) (++i + 96) + "";}for (int i = 0; i < data.length; i++) {dataHandler.handle(data[i]);}}
}

这段代码很简单,看出门道了吗?

对,他封装的区域正好跟上例是相反的

在上例中,我们封装了容器遍历的过程;而本例中,我们封装的是对每个具体元素要做的操作

我们定义了一个 DataHandler(数据手柄) 用于盛放我们要对每个具体元素要做的操作,然后把这个操作作为参数传递给 read 方法,我们把遍历的过程依然保留在 read 方法中,抽离出对每个元素具体的操作,这样也能实现我们的需求

这种写法在四人组的设计模式中把他称之为内部迭代器,因为迭代的过程依然被保留在容器的内部。但是事实上这种写法远不只是在迭代器中被使用,我们封装一段操作,然后把这段操作作为参数传给某个方法;然后当方法执行到符合某个条件时调用这段操作。

当你需要在很多操作的执行前和执行后执行一些固定的操作,而这些操作又不值当你用装饰者模式时,这种写法就是你的最优解。而且在JavaScript中,他还有个专有名词——回调函数



迭代器和其他模式

迭代器和组合

组合模式本身就是多个对象的聚合,所以应用组合模式的对象大多数都会使用迭代器模式


迭代器和状态

在我们改写A类型考勤读取器时,我们通过一个变量的不同状态来控制同一个迭代器的相同接口的行为,这本质上就是状态模式的一种应用




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容

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

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

相关文章

彻底解析:企业为何必须采用CRM系统以及其五大作用

相关数据显示&#xff0c;CRM系统在欧美发达国家的普及程度高&#xff0c;超出80%的企业部署了CRM管理系统。然而在国内这个比例依然很小只有10几%&#xff0c;为什么企业需要CRM系统&#xff1f;因为CRM可以为公司实现线索管理、绩效管理、销售流程管理、市场营销管理以及数据…

Python爬虫:设置随机 User-Agent

Python爬虫&#xff1a;设置随机 User-Agent 在Python中编写爬虫时&#xff0c;为了模拟真实用户的行为并防止被服务器识别为爬虫&#xff0c;通常需要设置随机的User-Agent。你可以使用fake-useragent库来实现这一功能。首先&#xff0c;你需要安装fake-useragent库&#xff…

C++进阶之路---继承(一)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、继承的概念及定义 1.继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0…

【爬虫】单首音乐的爬取(附源码)

以某狗音乐为例 import requests import re import time import hashlibdef GetResponse(url):# 模拟浏览器headers {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0}# 发送请求…

第五十回 插翅虎枷打白秀英 美髯公误失小衙内-mayfly-go:web 版 linux、数据库等管理平台

晁盖宋江和吴用到山下迎接雷横上山&#xff0c;宋江邀请雷横入伙&#xff0c;雷横以母亲年事已高为由拒绝了。 雷横回到郓城&#xff0c;听李小二说从东京新来了个表演的叫白秀英&#xff0c;吹拉弹唱跳&#xff0c;样样精通&#xff0c;于是雷横和李小二一起到戏院去看演出。…

Spring Webflux 详解

目录 0、组件对比 1、WebFlux 1、引入 2、Reactor Core 1、HttpHandler、HttpServer 3、DispatcherHandler 1、请求处理流程 4、注解开发 1、目标方法传参 2.返回值写法 5、文件上传 6、错误处理 7、RequestContext 8、自定义Flux配置 9、Filter WebFlux&am…

Java消息服务(JMS):在异步通信世界的引领者

文章目录 前言需求演进异步通信的需求增长面向消息的中间件兴起标准化的迫切需求 与相似框架的对比JMS vs AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;JMS vs MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;JMS vs Apache Kafka 完整的…

nginx,php-fpm

一&#xff0c;Nginx是异步非阻塞多进程&#xff0c;io多路复用 1、master进程&#xff1a;管理进程 master进程主要用来管理worker进程&#xff0c;具体包括如下4个主要功能&#xff1a; &#xff08;1&#xff09;接收来自外界的信号。 &#xff08;2&#xff09;向各worker进…

腾讯云服务器99元一年购买入口链接

腾讯云服务器99元一年购买入口链接如下&#xff0c;现在已经降价到61元一年&#xff0c;官方活动链接如下&#xff1a; 腾讯云99元服务器一年购买页面腾讯云活动汇聚了腾讯云最新的促销打折、优惠折扣等信息&#xff0c;你在这里可以找到云服务器、域名、数据库、小程序等等多种…

OSPF NSSA实验简述

OSPF NSSA实验简述 1、OSPF NSSA区域配置 为解决末端区域维护过大LSDB带来的问题&#xff0c;通过配置stub 区域或totally stub区域可以解决&#xff0c;但是他们都不能引入外部路由场景。 No so stuby area &#xff08;区域&#xff09;NSSA 可以引入外部路由&#xff0c;支持…

LLM 系列——BERT——论文解读

一、概述 1、是什么 是单模态“小”语言模型&#xff0c;是一个“Bidirectional Encoder Representations fromTransformers”的缩写&#xff0c;是一个语言预训练模型&#xff0c;通过随机掩盖一些词&#xff0c;然后预测这些被遮盖的词来训练双向语言模型&#xff08;编码器…

消息队列实现AB进程对话

进程A代码&#xff1a; #include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <stdlib.h>#include <string.h>#define MSG_EXCEPT 020000struct msgbuf{long mtype;char mtext[100];};int main(in…

3/5 work

1> 使用select实现tcp的服务器端&#xff0c;poll实现tcp的客户端&#xff08;君子作业&#xff09; 2> 将课堂上实现的模型重新自己实现一遍 #include<myhead.h> #define SER_IP "192.168.124.23" #define SER_PORT 8888 int main(int a…

html 文字滚动

<marquee> 标签 创建文字滚动的标签 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>wzgd</title></head><body><marquee direction"left" height"30" width"600&q…

python并发编程:IO模型

一 IO模型 二 network IO 再说一下IO发生时涉及的对象和步骤。对于一个network IO \(这里我们以read举例\)&#xff0c;它会涉及到两个系统对象&#xff0c;一个是调用这个IO的process \(or thread\)&#xff0c;另一个就是系统内核\(kernel\)。当一个read操作发生时&#xff…

USB - Linux Kernel Menuconfig

Linux kernel&#xff0c;make menuconfig&#xff0c;和USB相关的&#xff0c;在主菜单选择Device Drivers。 Device Drivers下面&#xff0c;找到USB support。 在USB support下面&#xff0c;就可以对USB相关的item进行设置。 按照从上到下的顺序&#xff0c;打开的设置依次…

java-ssm-jsp-宠物护理预定系统

java-ssm-jsp-宠物护理预定系统 获取源码——》公主号&#xff1a;计算机专业毕设大全

全球最强AI易主!OpenAI不是唯一的人工智能公司

近日&#xff0c;AI大模型初创公司Anthropic发布了备受瞩目的Claude 3系列模型&#xff0c;这一系列模型在多个AI评估指标上领先于业界标准LLM&#xff0c;成为全球最强的人工智能模型。这一突破不仅给OpenAI带来了巨大的压力&#xff0c;同时也展示了Anthropic在人工智能领域的…

【C++STL详解 —— string类】

【CSTL详解 —— string类】 CSTL详解 —— sring类一、string的定义方式二、string的插入三、string的拼接四、string的删除五、string的查找六、string的比较七、string的替换八、string的交换九、string的大小和容量十、string中元素的访问十一、string中运算符的使用十二、…

解决DBeaver执行脚本报错No active connection

解决DBeaver执行脚本报错No active connection 1、报错问腿 2、问题解决 2.1、右键点击该数据库&#xff0c;选择SQL编辑器&#xff0c;选择新建SQL编辑器&#xff0c;然后将sql语句复制过去。 或者左击选中数据库后直接使用快捷键 Ctrl] 2.2、在Project-General中找到Scr…