设计模式之迭代器模式
本文介绍设计模式中的迭代器模式,首先通俗的解释迭代器模式的基本概念和对应的四个角色,并根据四个角色举一个典型的实例,为了加强知识的连贯性,我们以Jdk源码集合中使用迭代器模式的应用进一步说明,最后说明迭代器模式的应用场景和优缺点。
读者可以拉取完整代码本地学习,实现代码均测试通过上传到码云
一、概念理解
迭代器模式官方解释就是提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。何为聚合对象呢?最典型的就是集合类。
大白话也就是,集合中的数据是私有的,集合中不应该提供直接遍历的方法,要定义一个新的对象用于访问这个集合。
既然是一个专门用来遍历的对象,一个被遍历的聚合对象,很显然至少有两个对象,迭代器对象、聚合对象,由于遵循面向接口编程的原则,迭代器对象和聚合对象应该抽象出来接口,那自然而然就是应该有四个角色:
抽象聚合(InterfaceAggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
基于四个角色我们举一个典型案例。
二、案例实现
应该是有四个类
抽象聚合角色,用于定义增删改查元素的统一规范接口,和创建迭代器对象的方法
具体聚合角色,实现抽象聚合角色方法
抽象迭代器角色,定义遍历元素的统一规范接口
具体迭代器,实现抽象迭代器角色的方法。
抽象聚合角色:
/**
* 抽象聚合角色
* @author tcy
* @Date 13-09-2022
*/
public interface InterfaceAggregate {
/**
* 增加对象
* @param obj 对象
*/
void add(Object obj);
/**
* 移除对象
* @param obj 对象
*/
void remove(Object obj);
/**
* 调用迭代器
* @return 迭代器
*/
Iterator getIterator();
}
具体聚合角色:
/**
* 具体聚合角色
* @author tcy
* @Date 13-09-2022
*/
public class ConcreteAggregate implements InterfaceAggregate{
private List<Object> list = new ArrayList<>();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Iterator getIterator() {
return new Concretelterator(list);
}
}
抽象迭代器角色:
/**
* 抽象迭代器
* @author tcy
* @Date 13-09-2022
*/
public interface Iterator<E> {
/**
* 删除对象
* @return 对象
*/
Object remove();
/**
* 调用下一个对象
* @return 对象
*/
E next();
/**
* 迭代器中是否还有下一个对象
* @return
*/
boolean hasNext();
/**
* 遍历迭代器中剩余的对象
* @param action
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
具体迭代器角色:
/**
* 具体迭代器角色
* @author tcy
* @Date 13-09-2022
*/
public class Concretelterator implements Iterator{
private List<Object> list = null;
private int index = -1;
public Concretelterator(List<Object> list) {
this.list = list;
}
@Override
public Object remove() {
index = list.size();
Object obj = list.get(index);
list.remove(obj);
return obj;
}
@Override
public Object next() {
Object obj = null;
if (this.hasNext()) {
obj = list.get(++index);
}
return obj;
}
@Override
public boolean hasNext() {
if (index < list.size() - 1) {
return true;
} else {
return false;
}
}
}
客户端调用:
/**
* @author tcy
* @Date 13-09-2022
*/
public class Client {
public static void main(String[] args) {
ConcreteAggregate concreteAggregate=new ConcreteAggregate();
concreteAggregate.add("老王");
concreteAggregate.add("小王");
concreteAggregate.add("小张");
System.out.println("Aggregate聚合对象有:");
Iterator iterator=concreteAggregate.getIterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next.toString());
}
//遍历剩下的角色
iterator.forEachRemaining(ele -> System.out.println(ele));
}
}
迭代器实现逻辑比较清晰,理解起来难度也不大,了解了该设计模式,趁热打铁看迭代器模式在源码中的应用。
三、源码应用
迭代器模式在Jdk中的集合类中有着广泛的应用,我们以ArrayList作为典型。
在ArrayList实现迭代器时,同样是有四个角色。
List抽象聚合类;
ArrayList具体聚合角色;
Iterator抽象迭代器;
ArrayList内部类Itr是具体迭代器;
我们可以看到ArrayList是把具体聚合角色和具体迭代器都写在一个类中,Itr作为具体迭代对象是以内部类的形式。
ArrayList其实和我们案例中的方法长的很像,只不过ArrayList中定义了更多的方法,而且ArrayList还有一个内部类ListItr。
其实是迭代器的增强版,在继承Itr的基础之上实现ListIterator接口。
Iterator迭代器除了,hasNext()、next()、remove()方法以外,还有一个特别的forEachRemaining()方法,我们重点说下forEachRemaining()方法,该方法代表的意思是遍历剩下的集合。
比如我们已经调用了该集合中的第一个元素,那么遍历时候就会自动忽略第一个元素,遍历剩下的元素。
我们写一个测试方法看效果:
public class Client {
public static void main(String[] args) {
// jdk ArrayList迭代器
//创建一个元素类型为Integer的集合
Collection<String> collection = new ArrayList<>();
//向集合中添加元素
collection.add("老王");
collection.add("小王");
collection.add("小张");
//获取该集合的迭代器
java.util.Iterator<String> iteratorJdk= collection.iterator();
System.out.println("Arraylist聚合对象有:");
//调用迭代器的经过集合实现的抽象方法遍历集合元素
while(iteratorJdk.hasNext())
{
System.out.println(iteratorJdk.next());
}
//调用forEachRemaining()方法遍历集合元素
iteratorJdk.forEachRemaining(ele -> System.out.println(ele));
}
}
Arraylist聚合对象有:
老王
小王
小张
正常情况下,会打印两次集合对象中的信息,实际上只打印了一次,正是由于next调用过的元素,forEachRemaining不会再调。
看到这,想必你对迭代器已经有了初步的了解,当在遍历元素时,除了使用for循环遍历元素以外,提供了另外一种方式遍历元素。
案例很好理解,源码中的应用也看得懂,但是实际开发中迭代器对象什么时候用呢?想必大部分人并不是很清晰。
接着看迭代器对象的应用场景和优缺点,看从中能不能找到答案。
四、总结
当一个对象是一个聚合对象且需要对外提供遍历方法时,可以使用迭代器模式,也即实际业务中定义的有聚合对象,里面存放了我们需要的业务数据,为了让业务数据的职责更清晰,我们就可以将编辑的方法提取出来,另外定义一个迭代器对象用于遍历数据。
迭代器方式提供了不同的方式遍历聚合对象,增加新的聚合类和迭代器类都是比较方便的,Java集合类中庞大的家族采用迭代器模式就是基于这种优点。
迭代器模式有设计模式的通用缺点——系统复杂性,迭代器模式将数据存储和数据遍历分开,增加了类的个数。
我已经连续更新了十几篇设计模式博客,推荐你一起学习。