16. 迭代器模式设计思想
16.迭代器模式设计思想
目录介绍
01.迭代器模式基础
1.1 迭代器模式由来
1.2 迭代器模式定义
1.3 迭代器模式场景
1.4 迭代器模式思考
1.5 主要解决的问题
02.迭代器模式原理
2.1 罗列一个场景
2.2 用例子理解迭代器
2.3 案例实现过程
2.4 迭代器实现步骤
03.迭代器模式结构
3.1 迭代器标准案例
3.2 迭代器模式结构图
3.3 迭代器模式时序图
04.迭代器模式案例
4.1 Java 中 Iterator 接口
4.2 迭代器遍历案例
4.3 遍历时不建议删除元素
4.4 for 循环 VS 迭代器
4.5 迭代器原理介绍
4.6 迭代器模式优势
05.迭代器者模式分析
5.1 迭代器模式优点
5.2 迭代器模式缺点
5.3 适用环境
5.4 思考题考察
06.观察者模式总结
6.1 总结一下学习
6.2 更多内容推荐
推荐一个好玩网站
一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网
关于设计模式,所有的代码都放到了该项目。设计模式大全
01.迭代器模式基础
1.1 迭代器模式由来
容器的种类有很多种,比如 ArrayList、LinkedList、HashSet...
每种容器都有自己的特点,ArrayList 底层维护的是一个数组;LinkedList 是链表结构的;HashSet 依赖的是哈希表,每种容器都有自己特有的数据结构。
因为容器的内部结构不同,很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单,Java 引入了迭代器模式!
不同的集合会对应不同的遍历方法,客户端代码无法复用。在实际应用中如何将两个不同集合整合是相当麻烦的。
所以才有 Iterator,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由 Iterator 来维护。
1.2 迭代器模式定义
迭代器模式为遍历集合中的元素提供了一种通用接口。
其核心思想是,将集合的遍历逻辑与集合对象的实现分离。通过迭代器,客户端代码无需关心集合的底层实现方式(如数组、链表等),就能顺序地访问集合的每一个元素。
1.3 迭代器模式场景
典型场景如下所示:
Java 集合框架:Java 的 Iterator 接口是迭代器模式的一个经典实现。诸如 List、Set 等集合类都支持使用迭代器来遍历元素。
文件处理:处理文件中的多行内容时,可以使用迭代器模式来顺序读取每一行,而不需要在意底层文件如何存储。
数据库结果集遍历:数据库查询返回的结果集可以通过迭代器模式逐条访问,不必关心结果集的存储细节。
1.4 迭代器模式思考
以下是一些关于迭代器模式的思考:
遍历过程中的修改:在使用迭代器遍历集合时,如果需要对集合进行修改,可能会引发一些问题。
封装集合:迭代器模式允许将集合的内部实现细节隐藏起来,只暴露一个统一的迭代器接口。
1.5 主要解决的问题
迭代器模式主要解决以下问题:
隐藏集合的内部结构:迭代器模式允许客户端代码遍历集合中的元素,而无需了解集合的内部结构。
统一遍历接口:迭代器模式定义了一个统一的迭代器接口,提高代码复用性和可读性。
安全地遍历集合:迭代器模式可以确保在遍历集合时不会对集合的结构造成破坏。
支持并发遍历:在多线程环境下,使用迭代器模式可以支持并发遍历集合。
02.迭代器模式原理
2.1 罗列一个场景
用 Java 代码实现一个简单的迭代器模式示例,场景为遍历一个字符串集合。
2.2 用例子理解迭代器
2.3 案例实现过程
1 迭代器接口,定义了遍历集合的方法
2 集合接口,定义了创建迭代器的方法
3 具体的集合类,包含一个字符串数组
4 具体的迭代器类,负责遍历字符串集合
5 测试迭代器模式
ConcreteAggregate:表示一个具体的集合,内部存储若干个字符串,并且实现了 Aggregate 接口,提供创建迭代器的功能。
ConcreteIterator:实现 Iterator 接口,内部持有对集合的引用,负责遍历集合中的元素。
IteratorPatternDemo:测试类,创建集合对象,并使用迭代器逐一访问集合中的元素。
2.4 迭代器实现步骤
Iterator(迭代器接口):定义了访问集合元素的方法。通常包括以下常见操作:
hasNext():检查是否还有下一个元素。
next():返回集合中的下一个元素。
remove():删除当前元素(可选)。
ConcreteIterator(具体迭代器类):实现 Iterator 接口,负责遍历具体集合中的元素。
Aggregate(集合接口):定义创建迭代器的接口。集合类通常提供一个 createIterator() 方法,用于返回一个 Iterator 对象。
ConcreteAggregate(具体集合类):实现集合接口,包含若干元素,并能够生成相应的迭代器对象。
Client(客户端):使用迭代器的地方。
03.迭代器模式结构
3.1 迭代器标准案例
3.2 迭代器模式结构图
角色职责说明
Aggregate:定义了创建迭代器的接口,负责管理集合中的元素。
ConcreteAggregate:具体的集合类,实现了创建迭代器的接口,并持有具体的数据。
Iterator:定义了用于遍历集合的方法。
ConcreteIterator:负责具体的迭代操作,内部维护一个指针来跟踪当前访问的元素。
3.3 迭代器模式时序图
04.迭代器模式案例
4.1 Java 中 Iterator 接口
看看 Java 中的 Iterator 接口是如何实现的。 在 Java 中 Iterator 为一个接口,它只提供了迭代的基本规则。
Iterable,Java 中还提供了一个 Iterable 接口,Iterable 接口实现后的功能是‘返回’一个迭代器
我们常用的实现了该接口的子接口有:Collection、List、Set 等。该接口的 iterator()方法返回一个标准的 Iterator 实现。实现 Iterable 接口允许对象成为 Foreach 语句的目标。就可以通过 foreach 语句来遍历你的底层序列。
Iterable 接口包含一个能产生 Iterator 对象的方法,并且 Iterable 被 foreach 用来在序列中移动。因此如果创建了实现 Iterable 接口的类,都可以将它用于 foreach 中。
Iterable 接口的具体实现:
4.2 迭代器遍历案例
如果要问 Java 中使用最多的一种模式,答案不是单例模式,也不是工厂模式,更不是策略模式,而是迭代器模式,先来看一段代码吧:
这个方法的作用是循环打印一个字符串集合,里面就用到了迭代器模式
java 语言已经完整地实现了迭代器模式,例如 List,Set,Map,而迭代器的作用就是把容器中的对象一个一个地遍历出来。
4.3 遍历时不建议删除元素
Iterator 遍历时不可以删除集合中的元素问题。在使用 Iterator 的时候禁止对所遍历的容器进行改变其大小结构的操作。
例如:在使用 Iterator 进行迭代时,如果对集合进行了 add、remove 操作就会出现 ConcurrentModificationException 异常。
在你迭代之前,迭代器已经被通过 list.iterator()创建出来了,如果在迭代的过程中,又对 list 进行了改变其容器大小的操作,那么 Java 就会给出异常。
因为此时 Iterator 对象已经无法主动同步 list 做出的改变,Java 会认为你做出这样的操作是线程不安全的,就会给出善意的提醒(抛出 ConcurrentModificationException 异常)
看一下 ArrayList 中的源码,看看迭代器中 next 和 remove 的实现方法代码。
查看源码发现原来检查并抛出异常的是 checkForComodification() 方法。在 ArrayList 中 modCount 是当前集合的版本号,每次修改(增、删)集合都会加 1;expectedModCount 是当前迭代器的版本号,在迭代器实例化时初始化为 modCount。
我们看到在 checkForComodification()方法中就是在验证 modCount 的值和 expectedModCount 的值是否相等,所以当你在调用了 ArrayList.add()或者 ArrayList.remove()时,只更新了 modCount 的状态,而迭代器中的 expectedModCount 未同步,因此才会导致再次调用 Iterator.next()方法时抛出异常。
但是为什么使用 Iterator.remove()就没有问题呢?通过源码的第 32 行发现,在 Iterator 的 remove()中同步了 expectedModCount 的值,所以当你下次再调用 next()的时候,检查不会抛出异常。
使用该机制的主要目的是为了实现 ArrayList 中的快速失败机制(fail-fast),在 Java 集合中较大一部分集合是存在快速失败机制的。
快速失败机制产生的条件:当多个线程对 Collection 进行操作时,若其中某一个线程通过 Iterator 遍历集合时,该集合的内容被其他线程所改变,则会抛出 ConcurrentModificationException 异常。
所以要保证在使用 Iterator 遍历集合的时候不出错误,就应该保证在遍历集合的过程中不会对集合产生结构上的修改。
4.4 for 循环 VS 迭代器
for 循环与迭代器的对比,效率上各有各的优势:
ArrayList 对随机访问比较快,而 for 循环中使用的 get()方法,采用的即是随机访问的方法,因此在 ArrayList 里 for 循环快。
LinkedList 则是顺序访问比较快,Iterator 中的 next()方法采用的是顺序访问方法,因此在 LinkedList 里使用 Iterator 较快。
4.5 迭代器原理介绍
迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。
它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。
迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
迭代器是用来遍历容器的
所以,一个完整的迭代器模式一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。
4.6 迭代器模式优势
一般来讲,遍历集合数据有三种方法:for 循环、foreach 循环、iterator 迭代器。对于这三种方式,我拿 Java 语言来举例说明一下。具体的代码如下所示:
实际上,foreach 循环只是一个语法糖而已,底层是基于迭代器来实现的。也就是说,上面代码中的第二种遍历方式(foreach 循环代码)的底层实现,就是第三种遍历方式(迭代器遍历代码)。这两种遍历方式可以看作同一种遍历方式,也就是迭代器遍历方式。
从上面的代码来看,for 循环遍历方式比起迭代器遍历方式,代码看起来更加简洁。那我们为什么还要用迭代器来遍历容器呢?为什么还要给容器设计对应的迭代器呢?原因有以下三个。
首先,对于类似数组和链表这样的数据结构,遍历方式比较简单,直接使用 for 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。如果由客户端代码来实现这些遍历算法,势必增加开发成本,而且容易写错。如果将这部分遍历的逻辑写到容器类中,也会导致容器类代码的复杂性。
其次,将游标指向的当前位置等信息,存储在迭代器类中,每个迭代器独享游标信息。这样,我们就可以创建多个不同的迭代器,同时对同一个容器进行遍历而互不影响。
最后,容器和迭代器都提供了抽象的接口,方便我们在开发的时候,基于接口而非具体的实现编程。当需要切换新的遍历算法的时候,比如,从前往后遍历链表切换成从后往前遍历链表,客户端代码只需要将迭代器类从 LinkedIterator 切换为 ReversedLinkedIterator 即可,其他代码都不需要修改。除此之外,添加新的遍历算法,我们只需要扩展新的迭代器类,也更符合开闭原则。
05.迭代器者模式分析
5.1 迭代器模式优点
①简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于 hash 表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。
②可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。
③封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。
5.2 迭代器模式缺点
对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,大家可能都有感觉,像 ArrayList,我们宁可愿意使用 for 循环和 get 方法来遍历集合。
5.3 适用环境
需要顺序访问集合对象的元素,但又不想暴露集合的底层实现。比如:有时我们希望从列表、栈、队列等数据结构中提取数据,但不希望直接操作这些数据结构的内部。
支持多种遍历方式。迭代器模式不仅仅能实现前向遍历,还可以实现其他方式的遍历,如双向遍历、逆序遍历等。
希望为不同的集合对象提供统一的遍历接口。无论是链表、数组,还是自定义的复杂数据结构,迭代器模式可以提供一种统一的遍历接口。
5.4 思考题考察
06.观察者模式总结
6.1 总结一下学习
迭代器模式,也叫游标模式。它用来遍历集合对象。这里说的“集合对象”,我们也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如,数组、链表、树、图、跳表。
一个完整的迭代器模式,一般会涉及容器和容器迭代器两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。
容器中需要定义 iterator() 方法,用来创建迭代器。迭代器接口中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。容器对象通过依赖注入传递到迭代器类中。
6.2 更多内容推荐
23 种设计模式
6.3 更多内容
我的编程网站:https://yccoding.com
Java 高级进阶专栏:https://github.com/yangchong211/YCJavaBlog
评论