您现在的位置是:首页 >技术杂谈 >设计模式之【迭代器模式】,对集合访问的统一网站首页技术杂谈
设计模式之【迭代器模式】,对集合访问的统一
文章目录
一、什么是迭代器模式
迭代器模式(Iterator Pattern)又称为游标模式(Cursor Pattern),它提供一种顺序访问集合/容器对象元素的方法,而又无需暴露集合内部表示。迭代器模式可以为不同的容器提供一致的遍历行为,而不用关心容器内容元素组成结构,属于行为型模式。
迭代器模式的本质是抽离集合对象迭代行为到迭代器中,提供一致访问接口。
1、迭代器模式使用场景
我们把多个对象聚在一起形成的总体称之为集合(Aggregate),集合对象是能够包容一组对象的容器对象。不同的集合其内部元素的聚合结构可能不同,而迭代器模式屏蔽了内部元素获取细节,为外部提供一致的元素访问行为,解耦了元素迭代与集合对象间的耦合,并且通过提供不同的迭代器,可以为同个集合对象提供不同顺序的元素访问行为(比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等),扩展了集合对象元素迭代功能,符合开闭原则。
迭代器模式适用于以下场景:
- 访问一个集合对象的内容而无需暴露它的内部表示;
- 为遍历不同的集合结构提供一个统一的访问接口;
- 需要为聚合对象提供多种遍历方式。
2、迭代器模式的优势
一般来讲,遍历集合数据有三种方法:for 循环、foreach 循环、iterator 迭代器。对于这三种方式,具体的代码如下所示:
List<String> names = new ArrayList<>();
names.add("aaa");
names.add("bbb");
names.add("ccc");
// 第一种遍历方式: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 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。如果由客户端代码来实现这些遍历算法,势必增加开发成本,而且容易写错。如果将这部分遍历的逻辑写到容器类中,也会导致容器类代码的复杂性。
我们可以将遍历操作拆分到迭代器类中。比如,针对图的遍历,我们就可以定义 DFSIterator、BFSIterator 两个迭代器类,让它们分别来实现深度优先遍历和广度优先遍历。
其次,将游标指向的当前位置等信息,存储在迭代器类中,每个迭代器独享游标信息。这样,我们就可以创建多个不同的迭代器,同时对同一个容器进行遍历而互不影响。
最后,容器和迭代器都提供了抽象的接口,方便我们在开发的时候,基于接口而非具体的实现编程。当需要切换新的遍历算法的时候,比如,从前往后遍历链表切换成从后往前遍历链表,客户端代码只需要将迭代器类从 LinkedIterator 切换为 ReversedLinkedIterator 即可,其他代码都不需要修改。除此之外,添加新的遍历算法,我们只需要扩展新的迭代器类,也更符合开闭原则。
3、迭代器模式的四种角色
- 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
- 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
4、迭代器模式的优缺点
优点:
- 多态迭代:为不同的聚合结构提供一致的遍历接口,即一个迭代接口可以访问不同的集合对象;
- 简化集合对象接口:迭代器模式将集合对象本身应该提供的元素迭代接口抽取到了迭代器中,使集合对象无须关心具体迭代行为;
- 元素迭代功能多样化:每个集合对象都可以提供一个或多个不同的迭代器,使得同种元素聚合结构可以有不同的迭代行为(比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等);
- 解耦迭代与集合:迭代器模式封装了具体的迭代算法,迭代算法的变化,不会影响到集合对象的架构。
缺点:
- 对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐。
在日常开发当中,我们几乎不会自己写迭代器。除非我们需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供给我们的API完全够用。
二、手写一个迭代器
1、迭代器模式的一般写法
//抽象迭代器
public interface Iterator<E> {
E next();
boolean hasNext();
}
//具体迭代器
public class ConcreteIterator<E> implements Iterator<E> {
private List<E> list;
private int cursor = 0;
public ConcreteIterator(List<E> list) {
this.list = list;
}
public E next() {
return this.list.get(this.cursor ++);
}
public boolean hasNext() {
return this.cursor < this.list.size();
}
}
//抽象容器
public interface IAggregate<E> {
boolean add(E element);
boolean remove(E element);
Iterator<E> iterator();
}
//具体容器
public class ConcreteAggregate<E> implements IAggregate<E> {
private List<E> list = new ArrayList<E>();
public boolean add(E element) {
return this.list.add(element);
}
public boolean remove(E element) {
return this.list.remove(element);
}
public Iterator<E> iterator() {
return new ConcreteIterator<E>(this.list);
}
}
public class Test {
public static void main(String[] args) {
//来一个容器对象
IAggregate<String> aggregate = new ConcreteAggregate<String>();
//添加元素
aggregate.add("one");
aggregate.add("two");
aggregate.add("three");
//获取容器对象迭代器
Iterator<String> iterator = aggregate.iterator();
//遍历
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
}
}
2、课程迭代器
// 迭代器接口
public interface Iterator<E> {
E next();
boolean hasNext();
}
public class IteratorImpl<E> implements Iterator<E> {
private List<E> list;
private int cursor;
private E element;
public IteratorImpl(List<E> list) {
this.list = list;
}
public E next() {
System.out.print("当前位置 " + cursor + " : ");
element = list.get(cursor);
cursor ++;
return element;
}
public boolean hasNext() {
if(cursor > list.size() - 1){
return false;
}
return true;
}
}
// 课程抽象容器
public interface ICourseAggregate {
void add(Course course);
void remove(Course course);
Iterator<Course> iterator();
}
// 具体容器
public class CourseAggregateImpl implements ICourseAggregate {
private List courseList;
public CourseAggregateImpl() {
this.courseList = new ArrayList();
}
public void add(Course course) {
courseList.add(course);
}
public void remove(Course course) {
courseList.remove(course);
}
public Iterator<Course> iterator() {
return new IteratorImpl<Course>(courseList);
}
}
public class Test {
public static void main(String[] args) {
Course java = new Course("Java架构");
Course javaBase = new Course("Java基础");
Course design = new Course("设计模式");
Course ai = new Course("人工智能");
ICourseAggregate aggregate = new CourseAggregateImpl();
aggregate.add(java);
aggregate.add(javaBase);
aggregate.add(design);
aggregate.add(ai);
System.out.println("===========课程列表==========");
printCourse(aggregate);
aggregate.remove(ai);
System.out.println("===========删除操作之后的课程列表==========");
printCourse(aggregate);
}
private static void printCourse(ICourseAggregate aggregate) {
Iterator<Course> i = aggregate.iterator();
while (i.hasNext()){
Course course = i.next();
System.out.println("《" + course.getName() + "》");
}
}
}
三、源码中的迭代器
在日常开发当中,我们几乎不会自己写迭代器。除非我们需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供给我们的API完全够用。
1、ArrayList
以下代码就是我们使用ArrayList的迭代器进行迭代的案例:
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
ArrayList的结构和我们上面的实例结构是一样的,只不过将其迭代器放到了内部类中了:
- List:抽象聚合类
- ArrayList:具体的聚合类
- Iterator:抽象迭代器
- list.iterator():返回的是实现了 Iterator 接口的具体迭代器对象
我们看一下Iterator的源码:
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
从Iterator代码中,主要定义了hasNext()和next()、remove()方法,和我们写的完全一致。
我们发现,迭代器模式和组合模式,两者似乎存在一定的相似性。组合模式解决的是统一树形结构各层次访问接口,迭代器模式解决的是统一各集合对象元素遍历接口。虽然他们的适配场景不同,但是核心理念是相通的。
Iterator的实现类,在ArrayList中是一个内部类:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
// prevent creating a synthetic constructor
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
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();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int size = ArrayList.this.size;
int i = cursor;
if (i < size) {
final Object[] es = elementData;
if (i >= es.length)
throw new ConcurrentModificationException();
for (; i < size && modCount == expectedModCount; i++)
action.accept(elementAt(es, i));
// update once at end to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable 并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。