文章目录
- 1 基本介绍
- 2 案例
- 2.1 Aggregate 接口
- 2.2 Iterator 接口
- 2.3 MyArray 类
- 2.4 MyArrayIterator 类
- 2.5 Client 类
- 2.6 Client 类的运行结果
- 2.7 总结
- 3 各角色之间的关系
- 3.1 角色
- 3.1.1 Aggregate ( 集合 )
- 3.1.2 Iterator ( 迭代器 )
- 3.1.3 ConcreteAggregate ( 具体的集合 )
- 3.1.4 ConcreteIterator ( 具体的迭代器 )
- 3.1.4 Client ( 客户端 )
- 3.2 类图
- 4 注意事项
- 5 在源码中的使用
- 6 优缺点
- 7 适用场景
- 8 总结
1 基本介绍
迭代器模式(Iterator Pattern)是一种 行为型 设计模式,它 提供了一种方法来按照某种顺序访问集合中的各个元素,而不需要暴露其内部表示。
2 案例
本案例实现了一个自定义的 MyArray
类,它可以存放指定数量的 Object
类型的对象,然后通过 MyArrayIterator
类的方法来获取并使用其中储存的元素。
2.1 Aggregate 接口
public interface Aggregate { // 集合接口boolean isEmpty(); // 检查集合是否为空boolean isFull(); // 检查集合是否已满Iterator iterator(); // 返回用于遍历集合的迭代器boolean add(Object obj); // 添加新元素,如果集合已满,则添加失败;否则添加成功int size(); // 返回集合中的元素个数
}
2.2 Iterator 接口
public interface Iterator { // 迭代器接口boolean hasNext(); // 判断是否存在下一个元素Object next(); // 获取下一个元素,同时将迭代器移动到下一个元素的位置
}
2.3 MyArray 类
public class MyArray implements Aggregate {private Object[] array; // 存放元素的数组private int size; // 当前数组中的元素个数public MyArray(int initSize) {array = new Object[initSize];}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic boolean isFull() {return size == array.length;}@Overridepublic Iterator iterator() {return new MyArrayIterator(this);}@Overridepublic boolean add(Object obj) {if (isFull()) {return false;}array[size++] = obj;return true;}@Overridepublic int size() {return size;}// 获取索引为 index 的元素,用于 MyArrayIterator 类的 next(),不应该被外部访问到protected Object get(int index) {return array[index];}
}
2.4 MyArrayIterator 类
public class MyArrayIterator implements Iterator {private MyArray myArray; // 待遍历的集合private int curr; // 当前元素索引,也就是迭代器public MyArrayIterator(MyArray myArray) {this.myArray = myArray;}@Overridepublic boolean hasNext() {return curr < myArray.size();}@Overridepublic Object next() {Object obj = myArray.get(curr); // 获取下一个元素curr++; // 将迭代器移动到下一个元素的位置return obj;}
}
2.5 Client 类
public class Client { // 客户端,测试 MyArray 和 MyArrayIteratorpublic static void main(String[] args) {Aggregate myArray = new MyArray(5);myArray.add("Hello, World!");myArray.add(2.0);myArray.add(3000);myArray.add(true);myArray.add('c');Iterator iterator = myArray.iterator();while (iterator.hasNext()) { // 如果集合中有剩余元素Object curr = iterator.next(); // 获取它System.out.println(curr); // 使用它}}
}
2.6 Client 类的运行结果
Hello, World!
2.0
3000
true
c
2.7 总结
通过 MyArrayIterator
迭代器的 hasNext(), next()
,Client
客户端无需知道 MyArray
集合的内部实现,只需要会使用这两个方法即可,这就 降低了客户端与集合之间的耦合性,增强了集合的扩展性。无论集合怎么变,只要它有对应的迭代器类,就可以使用如下的模版获取并使用集合中的所有元素。
Iterator iterator = ?.iterator(); // ? 代表集合的名称
while (iterator.hasNext()) { // 如果集合中有剩余元素Object curr = iterator.next(); // 获取它// 使用它
}
3 各角色之间的关系
3.1 角色
3.1.1 Aggregate ( 集合 )
该角色负责 定义 创建 Iterator 角色 的方法 和 集合应该具备 的方法。本案例中,Aggregate
接口扮演了该角色。
3.1.2 Iterator ( 迭代器 )
该角色负责 定义 按一定顺序逐个遍历元素的 接口。本案例中,Iterator
接口扮演了该角色。
3.1.3 ConcreteAggregate ( 具体的集合 )
该角色负责 实现 Aggregate 角色所定义的 方法,它可以创建 ConcreteIterator 角色。本案例中,MyArray
类扮演了该角色。
3.1.4 ConcreteIterator ( 具体的迭代器 )
该角色负责 实现 Iterator 角色定义的 方法,用于遍历对应的 ConcreteAggregate 角色。本案例中,MyArrayIterator
类扮演了该角色。
3.1.4 Client ( 客户端 )
该角色负责 使用 Aggregate 和 Iterator 角色实现业务逻辑,同时 创建了 ConcreteAggregate 和 ConcreteIterator 角色的对象。本案例中,Client
类扮演了该角色。
3.2 类图
4 注意事项
- 集合:
- 集合应隐藏内部表示:集合应确保通过迭代器访问其元素时,不暴露其内部数据结构。
- 集合应有一个创建迭代器的方法:集合应提供一个方法(如
iterator()
)来创建与之对应的迭代器实例。
- 迭代器:
- 明确迭代操作:迭代器接口应该明确定义迭代所需的操作,如
hasNext()
(是否有下一个元素)、next()
(获取下一个元素)等。 - 迭代器的状态管理:迭代器需要管理其 遍历状态,如 当前位置、是否遍历完所有元素 等。
- 通过迭代器访问元素:客户端 应通过迭代器接口来访问集合中的元素,而不是直接访问集合的内部数据结构。
- 迭代器的生命周期:迭代器的生命周期应与其对应的集合相关联。当集合被销毁或发生结构性变化时,相应的迭代器可能变得无效。
- 迭代器的有效性检查:在使用迭代器之前,应检查其是否有效。尝试使用无效的迭代器进行遍历可能会导致运行时错误。
- 迭代器的效率:迭代器的实现应 尽可能高效,以减少遍历集合时的性能开销。
- 迭代器的内存使用:在处理大型数据集时,应注意迭代器模式可能引入的额外内存使用,采取相应的优化措施。
- 明确迭代操作:迭代器接口应该明确定义迭代所需的操作,如
5 在源码中的使用
想必大家应该经常使用 JDK 中的 ArrayList
类,这个类就体现了迭代器模式的思想:
- Aggregate 角色:
List<E>
接口扮演该角色,定义了Iterator<E> iterator();
的方法:Iterator<E> iterator();
- Iterator 角色:
Iterator<E>
接口扮演该角色,定义了boolean hasNext(); E next();
这两个方法:boolean hasNext(); E next();
- ConcreteAggregate 角色:
ArrayList<E>
类扮演该角色,实现了Iterator<E> iterator();
方法:public Iterator<E> iterator() {return new Itr(); } transient Object[] elementData; // ArrayList 底层用于储存数据的数组
- ConcreteIterator 角色:
ArrayList<E>
类的内部类Itr
类扮演了该角色,实现了boolean hasNext(); E next();
这两个方法:int cursor; // 当前元素的索引 int lastRet = -1; // 最后一次调用 next() 前 cursor 的值 public boolean hasNext() {return cursor != size; }public E next() {checkForComodification(); // 无需关心本方法,本方法与迭代器模式无关int i = cursor;if (i >= size) // 在没有足够的元素时报错throw new NoSuchElementException();// 这里的 ArrayList.this.elementData 是外部类 ArrayList 的一个字段Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length) // 在索引越界时报错throw new ConcurrentModificationException();cursor = i + 1; // 指针指向下一个元素return (E) elementData[lastRet = i]; // 记录 cursor 移动之前的值 }
说明:在 Itr
类中没有发现其聚合了 ArrayList<E>
类的对象,这是因为 Itr
类是 ArrayList<E>
的内部类,直接就可以使用 ArrayList<E>
类的底层数据结构 Object[] elementData
。
6 优缺点
优点:
- 支持以不同的方式遍历一个聚合对象:迭代器简化了遍历方式,使得一个 ConcreteAggregate 可以通过不同的 ConcreteIterator 来以不同的方式遍历。
- 简化了集合的接口:由于引入了迭代器,集合不再需要暴露其内部表示和遍历算法,从而简化了集合的接口。
- 降低了系统的耦合性:迭代器模式使得遍历算法独立于集合,从而解开集合的 管理(增删改)与 遍历 之间的耦合。
- 提高了代码的复用性:无论集合的内部实现如何变化,迭代器都提供了统一的遍历接口,使得遍历集合的代码复用性很高。
- 支持“懒加载”:在某些情况下,迭代器可以只加载集合的部分元素,只有在真正需要元素时才加载它,这有助于节省内存和提高效率。
缺点:
- 增加了类的数量:在实现迭代器模式时,需要为每个 ConcreteAggregate 实现一个对应的 ConcreteIterator,这会增加类的数量,使得系统变得更加复杂。
- 迭代器与集合的耦合:虽然迭代器模式降低了集合的 管理 与 遍历 之间的耦合,但 迭代器本身 与 集合 之间存在一定的耦合关系,如果集合的内部实现发生较大变化,则可能需要修改迭代器的实现。
- 性能考虑:如果 集合中元素数量很少,或者 遍历操作不频繁,那么使用迭代器模式可能会降低性能,不如让集合直接暴露用于遍历的接口。
7 适用场景
- 提供统一接口:当 需要为不同的集合(如 数组、链表 等)提供 统一 的遍历方式 时,迭代器模式非常有用。通过迭代器,客户端代码可以不必了解集合的具体实现,只需通过迭代器接口进行遍历。
- 支持多种遍历方式:对于一个集合,可以通过不同的迭代器实现 提供多种遍历方式(如 正序遍历、倒序遍历 等),从而满足不同的遍历需求。
- 支持复杂数据结构的遍历:对于 树 和 图 等复杂数据结构,迭代器模式提供了简化的遍历接口。通过实现特定的迭代器类,可以轻松地使用 树 或 图 的 深度优先搜索(DFS)或 广度优先搜索(BFS)。
- 按需加载:在 处理大型数据集或无限数据流 时,迭代器模式支持 按需加载 数据元素。这意味着迭代器可以逐个处理数据项,而无需一次性将所有数据加载到内存中,从而提高了内存利用率和系统的响应速度。例如在处理大型文件时,可以使用迭代器模式逐行读取文件内容,而不是一次性将整个文件加载到内存中。
8 总结
迭代器模式 是一种 行为型 设计模式,它 使集合能够按照某种顺序被客户端遍历,而不需要暴露其内部表示,核心思想是 将集合的遍历行为抽象出来,放入一个独立的迭代器类中。
本模式将集合的 管理 与 遍历 分离开来,降低了 耦合性,使得遍历集合的代码具有很高的 复用性,而且可以实现 按需加载 的功能,从而节省内存空间。
但是这种模式会 增加类的数量,进而增加 系统复杂度。此外,本模式可能还会 降低性能,这就要求我们在使用本模式前仔细考虑 集合中的元素数量 和 遍历操作的频度 了。