遍历聚合对象中的元素——迭代器模式
迭代器模式概述
在软件开发中,我们经常需要使用聚合对象来存储一系列数据。聚合对象拥有两个职责:一 是存储数据;二是遍历数据。从依赖性来看,前者是聚合对象的基本职责;而后者既是可变 化的,又是可分离的。因此,可以将遍历数据的行为从聚合对象中分离出来,封装在一个被 称之为“迭代器”的对象中,由迭代器来提供遍历聚合对象内部数据的行为,这将简化聚合对象 的设计,更符合“单一职责原则”的要求。
迭代器模式定义如下:
迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象,而不用暴露这个对象的内部表 示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。
迭代器模式的类图如下:
迭代器模式由以下角色组成:
1) 迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。
2) 具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。
3) 容器角色(Container):容器角色负责提供创建具体迭代器角色的接口。
4) 具体容器角色(Concrete Container):具体容器角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该容器的结构相关。
在Java Collection的应用中,提供的具体迭代器角色是定义在容器角色中的内部类。这样便保护了容器的封装。但是同时容器也提供了遍历算法接口,你可以扩展自己的迭代器。
我们来看下Java Collection中的迭代器是怎么实现的吧。
//迭代器角色,仅仅定义了遍历接口 public interface Iterator { boolean hasNext(); Object next(); void remove(); } //容器角色,这里以List为例。它也仅仅是一个接口,就不罗列出来了 //具体容器角色,便是实现了List接口的ArrayList等类。为了突出重点这里指罗列和迭代器相关的内容 //具体迭代器角色,它是以内部类的形式出来的。AbstractList是为了将各个具体容器角色的公共部分提取出来而存在的。 public abstract class AbstractList extends AbstractCollection implements List { …… //这个便是负责创建具体迭代器角色的工厂方法 public Iterator iterator() { return new Itr(); } //作为内部类的具体迭代器角色 private class Itr implements Iterator { int cursor = 0; int lastRet = -1; int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public Object next() { checkForComodification(); try { Object next = get(cursor); lastRet = cursor++; return next; } catch(IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch(IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } |
自己实现一个迭代器:
1.创建接口:
Iterator.java
public interface Iterator { public boolean hasNext(); public Object next(); } |
Container.java
public interface Container { public Iterator getIterator(); } |
2.创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator。
NameRepository.java
public class NameRepository implements Container { public String names[] = {"Robert" , "John" ,"Julie" , "Lora"}; @Override public Iterator getIterator() { return new NameIterator(); } private class NameIterator implements Iterator { int index; @Override public boolean hasNext() { if(index < names.length){ return true; } return false; } @Override public Object next() { if(this.hasNext()){ return names[index++]; } return null; } } } |
3.使用 NameRepository 来获取迭代器,并打印名字。
IteratorPatternDemo.java
public class IteratorPatternDemo { public static void main(String[] args) { NameRepository namesRepository = new NameRepository(); for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){ String name = (String)iter.next(); System.out.println("Name : " + name); } } } |
执行程序,输出结果:
Name : Robert
Name : John
Name : Julie
Name : Lora
6 迭代器模式总结
迭代器模式是一种使用频率非常高的设计模式,通过引入迭代器可以将数据的遍历功能从聚 合对象中分离出来,聚合对象只负责存储数据,而遍历数据由迭代器来完成。由于很多编程 语言的类库都已经实现了迭代器模式,因此在实际开发中,我们只需要直接使用Java、C#等 语言已定义好的迭代器即可,迭代器已经成为我们操作聚合对象的基本工具之一。
1. 主要优点
迭代器模式的主要优点如下:
(1) 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方 式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法, 我们也可以自己定义迭代器的子类以支持新的遍历方式。
(2) 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍 历等方法,这样可以简化聚合类的设计。
(3) 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原 有代码,满足“开闭原则”的要求。
1. 主要缺点
迭代器模式的主要缺点如下:
(1) 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭 代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
(2) 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器 Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现, 而ListIterator迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面 的抽象迭代器并不是件很容易的事情。
1. 适用场景
在以下情况下可以考虑使用迭代器模式:
(1) 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储 分离,使得访问聚合对象时无须了解其内部实现细节。
(2) 需要为一个聚合对象提供多种遍历方式。
(3) 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供 不同的遍历方式,而客户端可以一致性地操作该接口。