🧑💻作者:猫十二懿
🎉公众号:猫十二懿
迭代器模式
1、迭代器模式介绍
迭代器模式是一种行为型设计模式,它提供了一种方法来访问聚合对象中的各个元素,而不暴露其内部表示。通过使用迭代器,客户端可以遍历一个聚合对象中的元素,而不必了解其内部实现。
在迭代器模式中,定义了一个迭代器接口,该接口声明了能够访问聚合对象中元素的方法。然后,具体的迭代器实现类实现了这个接口,并提供了一个具体的遍历算法。聚合对象则负责创建并返回其对应的迭代器实例。
例如:在以前坐公交得自己交钱,售票员根据上车的人进行售票,不管什么人都要买票(比如小偷、公交车公司内部人员、程序员等同样要买票人人平等)。售票员其实就是将所有人都遍历了一遍,对每个乘客都要买票。
1.1 迭代器模式基本实现
迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
迭代器模式结构图:
Aggregate聚集抽象类:
/**
* @author Shier
* CreateTime 2023/5/13 21:07
* 聚集抽象类
*/
public abstract class Aggregate {
/**
* 创建迭代器
*
* @return
*/
public abstract Iterator createIterator();
}
ConcreteAggregate具体聚集类:继承Aggregate。
/**
* @author Shier
* CreateTime 2023/5/13 21:09
* 具体聚集类
*/
public class ConcreteAggregate extends Aggregate {
/**
* 存储聚合对象
*/
private List<Object> list = new ArrayList<>();
@Override
public Iterator createIterator() {
return (Iterator) new ConcreteIterator(this);
}
/**
* 返回聚合总个数
*
* @return
*/
public int getCount() {
return list.size();
}
/**
* 增加新对象
*
* @param obj
*/
public void add(Object obj) {
list.add(obj);
}
/**
* 得到指定的对象
*/
public Object getCurrentItem(int index) {
return list.get(index);
}
}
Iterator迭代器抽象类:
/**
* @author Shier
* CreateTime 2023/5/13 21:16
* Iterator 迭代器对象类
*/
public abstract class Iterator {
// 第一个
public abstract Object first();
// 下一个
public abstract Object netx();
// 是否到最后
public abstract boolean isDone();
// 当前对象
public abstract Object currentItems();
}
ConcreteIterator具体迭代器类:继承Iterator。
/**
* @author Shier
* CreateTime 2023/5/13 21:15
*/
public class ConcreteIterator extends Iterator {
private ConcreteAggregate aggregate;
public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
}
private int current = 0;
/**
* 得到第一个对象
*
* @return
*/
@Override
public Object first() {
return aggregate.getCurrentItem(0);
}
/**
* 得到下一个对象
*
* @return
*/
@Override
public Object netx() {
Object ret = null;
current++;
if (current < aggregate.getCount()) {
ret = aggregate.getCurrentItem(current);
}
return ret;
}
/**
* 判断是否到结尾
*
* @return
*/
@Override
public boolean isDone() {
return current >= aggregate.getCount() ? true : false;
}
/**
* 返回当前对象
*
* @return
*/
@Override
public Object currentItems() {
return aggregate.getCurrentItem(current);
}
}
客户端代码:
/**
* @author Shier
* CreateTime 2023/5/13 21:27
*/
public class BaseClient {
public static void main(String[] args) {
// 聚集对象 上面的例子就相当于公交车bus
ConcreteAggregate aggregateBus = new ConcreteAggregate();
aggregateBus.add("shier");
aggregateBus.add("公交公司员工");
aggregateBus.add("小菜");
aggregateBus.add("大白");
aggregateBus.add("小黑");
aggregateBus.add("小偷");
// 迭代器对象声明,即相当于售票员
ConcreteIterator conductor = new ConcreteIterator(aggregateBus);
// 向第一个乘客售票
conductor.first();
while (!conductor.isDone()) {
// 没有到最后一个则一直走向下一个乘客
System.out.println(conductor.currentItems() + ": 请买票!");
conductor.netx();
}
}
}
输出结果:
同时还可以实现倒序:
/**
* @author Shier
* CreateTime 2023/5/13 21:15
* 具体迭代器类 - 倒序
*/
public class ConcreteIteratorDesc extends Iterator {
private ConcreteAggregate aggregate;
private int current = 0;
public ConcreteIteratorDesc(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
// 从最后一个开始
current = aggregate.getCount() - 1;
}
/**
* 得到倒数第一个对象
*
* @return
*/
@Override
public Object first() {
return aggregate.getCurrentItem(aggregate.getCount() - 1);
}
/**
* 得到下一个对象
*
* @return
*/
@Override
public Object netx() {
Object ret = null;
// 递减
current--;
if (current < aggregate.getCount()) {
ret = aggregate.getCurrentItem(current);
}
return ret;
}
/**
* 判断是否到结尾
*
* @return
*/
@Override
public boolean isDone() {
return current >= aggregate.getCount() ? true : false;
}
/**
* 返回当前对象
*
* @return
*/
@Override
public Object currentItems() {
return aggregate.getCurrentItem(current);
}
}
你想呀,售票员才不管你上来的是人还是物(行李),不管是中国人还 是外国人,不管是不是内部员工,甚至哪怕是马上要抓走的小偷,只要是来 乘车的乘客,就必须要买票。同样道理,当你需要访问一个聚集对象,而且 不管这些对象是什么都需要遍历的时候,你就应该考虑用迭代器模式。对聚集有多种方式遍历时,可以考虑用迭代器模式。售票员从车头到车尾来售票,也可以从车尾向车头来售票,也就是说, 你需要==对聚集有多种方式遍历时,可以考虑用迭代器模式==。由于不管乘客是 什么,售票员的做法始终是相同的,都是从第一个开始,下一个是谁,是否 结束,当前售到哪个人了,这些方法每天他都在做,也就是说,为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。
2、具体例子说明
2.1 购物车案例
上面也是举例一个坐公交车的列子
下面在用一个例子说明:
- 假设有一个超市的购物车,其中包含了多种商品,比如苹果、橙子、香蕉等。这个购物车可以看作是一个聚合对象,而其中每种商品则可以看作是购物车中的元素。
如果要遍历购物车中的所有商品,可以使用迭代器模式来实现。首先,定义一个迭代器接口,声明能够访问购物车中商品的方法。比如,可以定义一个
Iterator
接口,其中包含hasNext()
和next()
两个方法。
然后,具体的迭代器实现类可以实现这个接口,并提供一个具体的遍历算法。例如,可以定义一个
CartIterator
类,它维护了购物车的内部状态,并实现了hasNext()
和next()
方法,通过这两个方法来依次返回购物车中的每个商品。最后,购物车可以负责创建并返回其对应的迭代器实例。可以定义一个
Cart
类,其中包含了购物车中所有商品的列表,以及一个getIterator()
方法,用于返回一个CartIterator
实例。这样,客户端就可以通过调用
getIterator()
方法来获取购物车对应的迭代器,并使用它来遍历购物车中的所有商品。这种实现方式让遍历算法与聚合对象分离开来,使代码更加灵活和易于维护。
首先,定义迭代器接口 CartIterator
:
public interface CartIterator {
boolean hasNext();
Object next();
}
然后,定义具体的迭代器实现类 CartIteratorImpl
:
public class CartIteratorImpl implements CartIterator {
private List<Object> cartList; // 购物车中的商品列表
private int position; // 迭代器当前位置
public CartIteratorImpl(List<Object> cartList) {
this.cartList = cartList;
this.position = 0;
}
@Override
public boolean hasNext() {
return position < cartList.size();
}
@Override
public Object next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Object item = cartList.get(position);
position++;
return item;
}
}
在 CartIteratorImpl
类中,我们使用了一个 List<Object>
来保存购物车中的商品列表,在 hasNext()
和 next()
方法中维护了内部状态 position
,以便顺序地遍历购物车中的每个商品。
最后,定义聚合对象 Cart
,并在其中实现 getIterator()
方法:
public class Cart {
private List<Object> itemList;
public Cart() {
this.itemList = new ArrayList<>();
}
public void addItem(Object item) {
itemList.add(item);
}
public void removeItem(Object item) {
itemList.remove(item);
}
public CartIterator getIterator() {
return new CartIteratorImpl(itemList);
}
}
在 Cart
类中,我们使用了一个 List<Object>
来保存购物车中的商品列表,实现了添加和删除商品的方法。同时,实现了 getIterator()
方法,用于返回购物车对应的迭代器实例 CartIteratorImpl
。
最后,可以在客户端代码中使用 Cart
和 CartIterator
来遍历购物车中的所有商品:
public class Client {
public static void main(String[] args) {
Cart cart = new Cart();
cart.addItem("苹果");
cart.addItem("橙子");
cart.addItem("香蕉");
CartIterator iterator = cart.getIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
输出结果:
苹果
橙子
香蕉
2.2 Java迭代器实现
起始在开发过程中不会向上面那样去搞,因为目前很多开发语言以及接入了这个迭代器模式,比如在Java中以及有了一个Iterator类
Java 中的 Iterator
类是迭代器模式的一种典型实现。它提供了一种统一的遍历方式,可以对不同类型的集合进行遍历,而无需关心集合内部的实现细节。
在 Java 中,每个实现了 Iterable
接口的对象都可以返回一个 Iterator
对象,用于遍历它所包含的元素。Iterator
接口定义了三个方法:
boolean hasNext()
:判断集合中是否还有下一个元素;E next()
:返回集合中的下一个元素;void remove()
:在迭代过程中移除集合中的当前元素,可选操作。
通过调用 hasNext()
和 next()
方法,可以依次访问集合中的每个元素,直到遍历完所有元素为止。在遍历过程中,Iterator
对象负责维护内部状态,以便正确地返回下一个元素。
在迭代器模式中,迭代器对象将遍历算法和集合对象分离开来,避免了暴露集合内部实现细节,并将集合的遍历行为抽象为一个独立的接口。这使得代码更灵活、可扩展、易于维护。
比如,在 Java 中,可以使用 ArrayList
、LinkedList
、HashSet
等不同类型的集合类,并使用 Iterator
接口来实现对它们的遍历,而无需关心它们内部的实现细节。
3、迭代器模式总结
迭代器模式是一种行为型设计模式,它提供了一种访问集合对象内部元素的方式,而不用暴露集合的内部细节。
在迭代器模式中,集合对象和迭代器对象分别负责实现集合和遍历算法,并相互独立地进行演化和修改。这样可以避免暴露集合内部结构,也方便对集合进行扩展和修改。
迭代器模式主要由四个角色组成:
- 抽象聚合类(
Aggregate
):定义集合对象的接口,包括添加、删除元素等方法。 - 具体聚合类(
ConcreteAggregate
):实现抽象聚合类接口,存储集合中的元素。 - 抽象迭代器类(
Iterator
):定义遍历集合的接口,包括获取下一个元素、判断是否还有下一个元素等方法。 - 具体迭代器类(
ConcreteIterator
):实现抽象迭代器接口,负责对集合进行遍历操作。
迭代器模式的优点包括:
- 将集合对象和遍历算法分离,使得代码更灵活、可扩展、易于维护。
- 对客户端隐藏集合对象的内部实现,提高了代码的安全性。
- 支持对同一种数据结构进行不同方式的遍历。
迭代器模式的缺点包括:
- 需要实现迭代器对象和聚合对象,增加了代码复杂度。
- 在集合内部元素发生变化时,需要及时更新迭代器状态,否则可能导致遍历结果不正确。
现在很多的开发语言都已经内置了迭代器模式,不要我们自己再去定义组件的迭代器(不能排除有特殊需求,还是得自己定义迭代器)
比如:
- 在 C# 中,集合类内部实现了
IEnumerable
和IEnumerator
接口,这两个接口就对应了迭代器模式中的抽象聚合类和抽象迭代器类。同时,C# 也提供了yield
关键字,方便使用迭代器模式进行集合遍历。- Python 中的
__iter__()
和__next__()
方法,Ruby 中的each
方法等- 在 Java 中,集合框架中的
Iterator
接口和相应的实现类就是迭代器模式的典型实现。同时,Java 8 还引入了基于 Lambda 表达式的 Stream API,它提供了非常便捷的集合遍历方式,并且可以进行各种数据处理和转换操作,进一步提高了代码的可读性、可维护性和可重用性。
更新迭代器状态,否则可能导致遍历结果不正确。
现在很多的开发语言都已经内置了迭代器模式,不要我们自己再去定义组件的迭代器(不能排除有特殊需求,还是得自己定义迭代器)
比如:
- 在 C# 中,集合类内部实现了
IEnumerable
和IEnumerator
接口,这两个接口就对应了迭代器模式中的抽象聚合类和抽象迭代器类。同时,C# 也提供了yield
关键字,方便使用迭代器模式进行集合遍历。- Python 中的
__iter__()
和__next__()
方法,Ruby 中的each
方法等- 在 Java 中,集合框架中的
Iterator
接口和相应的实现类就是迭代器模式的典型实现。同时,Java 8 还引入了基于 Lambda 表达式的 Stream API,它提供了非常便捷的集合遍历方式,并且可以进行各种数据处理和转换操作,进一步提高了代码的可读性、可维护性和可重用性。