迭代器模式
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,它提供了非常便捷的集合遍历方式,并且可以进行各种数据处理和转换操作,进一步提高了代码的可读性、可维护性和可重用性。