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 迭代器接口,定义了遍历集合的方法
interface Iterator<T> {
boolean hasNext();
T next();
}
2 集合接口,定义了创建迭代器的方法
interface Aggregate<T> {
Iterator<T> createIterator();
}
3 具体的集合类,包含一个字符串数组
class ConcreteAggregate implements Aggregate<String> {
private String[] items;
private int size;
public ConcreteAggregate(int size) {
this.size = size;
items = new String[size];
for (int i = 0; i < size; i++) {
items[i] = "Item " + (i + 1);
}
}
// 创建具体的迭代器
@Override
public Iterator<String> createIterator() {
return new ConcreteIterator(this);
}
// 获取集合的大小
public int getSize() {
return size;
}
// 通过索引获取集合中的元素
public String getItem(int index) {
if (index < size) {
return items[index];
}
return null;
}
}
4 具体的迭代器类,负责遍历字符串集合
class ConcreteIterator implements Iterator<String> {
private ConcreteAggregate aggregate;
private int currentIndex = 0;
public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
}
// 判断是否还有下一个元素
@Override
public boolean hasNext() {
return currentIndex < aggregate.getSize();
}
// 获取下一个元素
@Override
public String next() {
return aggregate.getItem(currentIndex++);
}
}
5 测试迭代器模式
public class IteratorPatternDemo {
public static void main(String[] args) {
// 创建具体集合
ConcreteAggregate aggregate = new ConcreteAggregate(5);
// 创建迭代器
Iterator<String> iterator = aggregate.createIterator();
// 使用迭代器遍历集合
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
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为一个接口,它只提供了迭代的基本规则。
package java.util;
public interface Iterator<E> {
boolean hasNext();//判断是否存在下一个对象元素
E next();//获取下一个元素
void remove();//移除元素
}
Iterable,Java中还提供了一个Iterable接口,Iterable接口实现后的功能是‘返回’一个迭代器
我们常用的实现了该接口的子接口有:Collection
Iterable接口包含一个能产生Iterator对象的方法,并且Iterable被foreach用来在序列中移动。因此如果创建了实现Iterable接口的类,都可以将它用于foreach中。
Iterable接口的具体实现:
import java.util.Iterator;
public interface Iterable<T> {
Iterator<T> iterator();
}
4.2 迭代器遍历案例
如果要问Java中使用最多的一种模式,答案不是单例模式,也不是工厂模式,更不是策略模式,而是迭代器模式,先来看一段代码吧:
public static void print(Collection coll){
Iterator it = coll.iterator();
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
}
}
这个方法的作用是循环打印一个字符串集合,里面就用到了迭代器模式
java语言已经完整地实现了迭代器模式,例如List,Set,Map,而迭代器的作用就是把容器中的对象一个一个地遍历出来。
4.3 遍历时不建议删除元素
Iterator遍历时不可以删除集合中的元素问题。在使用Iterator的时候禁止对所遍历的容器进行改变其大小结构的操作。
例如:在使用Iterator进行迭代时,如果对集合进行了add、remove操作就会出现ConcurrentModificationException异常。
在你迭代之前,迭代器已经被通过list.iterator()创建出来了,如果在迭代的过程中,又对list进行了改变其容器大小的操作,那么Java就会给出异常。
因为此时Iterator对象已经无法主动同步list做出的改变,Java会认为你做出这样的操作是线程不安全的,就会给出善意的提醒(抛出ConcurrentModificationException异常)
List<String> list = new ArrayList<String>();
list.add("张三1");
list.add("张三2");
list.add("张三3");
list.add("张三4");
//……
list.add("张三N");
//使用迭代器遍历ArrayList集合
Iterator<String> listIt = list.iterator();
while(listIt.hasNext()){
Object obj = listIt.next();
if(obj.equals("张三100")){
list.remove(obj);
}
}
看一下ArrayList中的源码,看看迭代器中next和remove的实现方法代码。
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
// Android-changed: Add "limit" field to detect end of iteration.
// The "limit" of this iterator. This is the size of the list at the time the
// iterator was created. Adding & removing elements will invalidate the iteration
// anyway (and cause next() to throw) so saving this value will guarantee that the
// value of hasNext() remains stable and won't flap between true and false when elements
// are added and removed from the list.
protected int limit = ArrayList.this.size;
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
public boolean hasNext() {
return cursor < limit;
}
@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
limit--;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
查看源码发现原来检查并抛出异常的是 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 语言来举例说明一下。具体的代码如下所示:
List<String> names = new ArrayList<>();
names.add("xzg");
names.add("wang");
names.add("zheng");
// 第一种遍历方式:for循环
for (int i = 0; i < names.size(); i++) {
System.out.print(names.get(i) + ",");
}
// 第二种遍历方式:foreach循环
for (String name : names) {
System.out.print(name + ",")
}
// 第三种遍历方式:迭代器遍历
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");//Java中的迭代器接口是第二种定义方式,next()既移动游标又返回数据
}
实际上,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 更多内容推荐
模块 | 描述 | 备注 |
---|---|---|
GitHub | 多个YC系列开源项目,包含Android组件库,以及多个案例 | GitHub |
博客汇总 | 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 | YCBlogs |
设计模式 | 六大设计原则,23种设计模式,设计模式案例,面向对象思想 | 设计模式 |
Java进阶 | 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM | Java高级 |
网络协议 | 网络实际案例,网络原理和分层,Https,网络请求,故障排查 | 网络协议 |
计算机原理 | 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 | 计算机基础 |
学习C编程 | C语言入门级别系统全面的学习教程,学习三到四个综合案例 | C编程 |
C++编程 | C++语言入门级别系统全面的教学教程,并发编程,核心原理 | C++编程 |
算法实践 | 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 | Leetcode |
Android | 基础入门,开源库解读,性能优化,Framework,方案设计 | Android |
23种设计模式
23种设计模式 & 描述 & 核心作用 | 包括 |
---|---|
创建型模式 提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离 |
工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
结构型模式 关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构 |
适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
行为型模式 特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题 |
责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
6.3 更多内容
- GitHub:https://github.com/yangchong211
- 我的编程网站:https://yccoding.com
- 博客汇总:https://github.com/yangchong211/YCBlogs
- 设计模式专栏:https://github.com/yangchong211/YCDesignBlog
- Java高级进阶专栏:https://github.com/yangchong211/YCJavaBlog
- 网络协议专栏:https://github.com/yangchong211/YCNetwork
- 计算机基础原理专栏:https://github.com/yangchong211/YCComputerBlog