一、什么是Iterator模式
使用Java语言显示数组arr中的元素时,我们可以使用下面这样的for循环语句遍历数组。
for (int i = 0; i < arr.length; i++){ system.out.println(arr[i]); }
请注意这段代码中的循环变量i。该变量的初始值是o,然后会递增为1,2,3,...,程序则在每次i递增后都输出arr[i]。我们在程序中经常会看到这样的for循环语句。
for语句中的i++的作用是让i的值在每次循环后自增1,这样就可以访问数组中的下一个元素、下下一个元素、再下下一个元素,也就实现了从头至尾逐一遍历数组元素的功能。
将这里的循环变量i的作用抽象化、通用化后形成的模式,在设计模式中称为Iterator模式。lterator模式用于在数据集合中按照顺序遍历集合。英语单词Iterate有反复做某件事情的意思,汉语称为“迭代器”。
用一句话来概括:一个一个遍历
二、Iterator模式示例代码
首先,让我们来看一段实现了Iterator模式的示例程序。这段示例程序的作用是将书(Book)放置到书架(BookShelf)中,并将书的名字按顺序显示出来。
2.1 类之间的关系
类和接口的功能:
类图:
2.2 Aggregate接口
Aggregate接口是所要遍历的集合的接口。实现了该接口的类将成为一个可以保存多个元素的集合,就像数组一样。Aggregate有“使聚集”、“集合”的意思。
public interface Aggregate { public abstract Iterator iterator(); }
2.3 lterator接口
Iterator接口用于遍历集合中的元素,其作用相当于循环语句中的循环变量。
这里有必要说明一下next方法。该方法的返回类型是Object,这表明该方法返回的是集合中的一个元素。但是,next方法的作用并非仅仅如此。为了能够在下次调用next方法时正确地返回下一个元素,该方法中还隐含着将迭代器移动至下一个元素的处理。说“隐含”,是因为Iterator接口只知道方法名。想要知道next方法中到底进行了什么样的处理,还需要看一下实现了Iterator接口的类( BookShelfIterator)。这样,我们才能看懂next方法的作用。
/** * 用于遍历集合中的元素 */ public interface Iterator { /** * 判断是否存在下一个元素 * @return 当集合中存在下一个元素时,该方法返回true * 当集合中不存在下一个元素,即已经遍历至集合末尾时,返回false */ public abstract boolean hasNext(); /** * 获取下一个元素 * @return 下一个元素 */ public abstract Object next(); }
2.4 Book类
Book类是表示书的类。
public class Book { //书名 private String name; public Book(String name) { this.name = name; } /** * 获取书的名字 */ public String getName() { return name; } }
2.5 BookShelf类
BookShelf类是表示书架的类。由于需要将该类作为集合进行处理,因此它实现了Aggregate接口。此外,请注意在BookShelf类中还实现了Aggregate接口的iterator方法。
之所以将books字段的可见性设置为private,是为了防止外部不小心改变了该字段的值。
接下来我们看看iterator方法。该方法会生成并返回BookShelfIterator类的实例作为BookShelf类对应的Iterator。当外部想要遍历书架时,就会调用这个方法。
public class BookShelf implements Aggregate{ //书架上的书。数组大小在构造函数中指定 private Book[] books; private int last = 0; public BookShelf(int maxsize) { this.books = new Book[maxsize]; } public Book getBookAt(int index) { return books[index]; } public void appendBook(Book book) { this.books[last] = book; last++; } public int getLength() { return last; } @Override public Iterator iterator() { return new BookShelfIterator(this); } }
2.6 BookShelflterator类
用于遍历书架的类。
public class BookShelfIterator implements Iterator { //要遍历的书架 private BookShelf bookShelf; //迭代器当前所指向的书的下标 private int index; public BookShelfIterator(BookShelf bookShelf) { this.bookShelf = bookShelf; this.index = 0; } /** * 判断书架中还有没有下一本书 * @return 如果有就返回true,如果没有就返回false */ @Override public boolean hasNext() { //比较index和书架中书的总册数 if (index < bookShelf.getLength()) { return true; } else { return false; } } /** * 返回迭代器当前所指向的书(Book的实例),并让迭代器指向下一本书 */ @Override public Object next() { Book book = bookShelf.getBookAt(index); index++; return book; } }
2.7 Main类
public class Main { public static void main(String[] args) { BookShelf bookShelf = new BookShelf(4); bookShelf.appendBook(new Book("Romance of the Three Kingdoms")); bookShelf.appendBook(new Book("A Dream in Red Mansions")); bookShelf.appendBook(new Book("The Story of Stone")); bookShelf.appendBook(new Book("Journey to the West")); Iterator it = bookShelf.iterator(); while (it.hasNext()) { Book book = (Book) it.next(); System.out.println(book.getName()); } } }
2.8 运行结果
通过bookShelf.iterator()得到的it是用于遍历书架的Iterator实例。while部分的条件当然就是it.hasNext()了。只要书架上有书,while循环就不会停止。然后,程序会通过it.next()一本—本地遍历书架中的书。
三、拓展思路的要点
3.1 不管实现如何变化,都可以使用lterator
为什么一定要考虑引入Iterator这种复杂的设计模式呢?如果是数组,直接使用for循环语句进行遍历处理不就可以了吗?为什么要在集合之外引入Iterator这个角色呢?
一个重要的理由是,引入 Iterator后可以将遍历与实现分离开来。请看下面的代码。
while (it.hasNext ()){ Book book =(Book)it.next(); System.out.println(book.getName()); )
这里只使用了Iterator的 hasNext方法和next方法,并没有调用BookShelf的方法。也就是说,这里的while循环并不依赖于BookShelf的实现。
如果编写BookShelf的开发人员决定放弃用数组来管理书本,而是用java.util.vector取而代之,会怎样呢?不管BookShelf如何变化,只要BookShelf的iterator方法能正确地返回Iterator的实例(也就是说,返回的Iterator类的实例没有问题,hasNext和next方法都可以正常工.作),即使不对上面的while循环做任何修改,代码都可以正常工作。
这对于BookShelf的调用者来说真是太方便了。设计模式的作用就是帮助我们编写可复用的类。所谓“可复用”,就是指将类实现为“组件”,当一个组件发生改变时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。
这样也就能理解为什么在示例程序中iterator方法的返回值不是BookShelfIterator类型而是Iterator类型了。这表明,这段程序就是要使用Iterator的方法进行编程,而不是BookShelfIterator的方法。
3.2 难以理解抽象类和接口
难以理解抽象类和接口的人常常使用ConcreteAggregate 角色和Concretelterator角色编程,而不使用Aggregate接口和Iterator接口,他们总想用具体的类来解决所有的问题。
但是如果只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。
3.3 Aggregate和 Iterator的对应
请大家仔细回忆一下我们是如何把BookShelfIterator类定义为BookShelf类的Concretelterator 角色的。BookShelfIterator类知道BookShelf是如何实现的。也正是因为如此,我们才能调用用来获取下一本书的getBookAt方法。
也就是说,如果BookShelf的实现发生了改变,即getBookAt方法这个接口(API)发生变化时,我们必须修改BookShelfIterator类。
正如Aggregate和Iterator这两个接口是对应的一样,ConcreteAggregate和concreteIterator这两个类也是对应的。
3.4 容易弄错“下一个”
在Iterator模式的实现中,很容易在next方法上出错。该方法的返回值到底是应该指向当前元素还是当前元素的下一个元素呢?更详细地讲,next方法的名字应该是下面这样的。
returnCurrentElementAndAdvanceToNextPosition
也就是说,next方法是“返回当前的元素,并指向下一个元素”。
3.5 还容易弄错“最后一个”
在Iterator模式中,不仅容易弄错“下一个”,还容易弄错“最后一个”。hasNext方法在返回最后一个元素前会返回true,当返回了最后一个元素后则返回false。稍不注意,就会无法正确地返回“最后一个”元素。
请大家将hasNext方法理解成“确认接下来是否可以调用next方法”的方法就可以了。多个lterator
“将遍历功能置于Aggregate角色之外”是Iterator模式的一个特征。根据这个特征,可以针对一个ConcreteAggregate角色编写多个ConcreteIterator角色。
3.6 迭代器的种类多种多样
在示例程序中展示的Iterator类只是很简单地从前向后遍历集合。其实,遍历的方法是多种多样的。
从最后开始向前遍历
既可以从前向后遍历,也可以从后向前遍历(既有next方法也有previous方法)
指定下标进行“跳跃式”遍历
学到这里,相信大家应该可以根据需求编写出各种各样的Iterator类了。
3.7 不需要deletelterator
在Java中,没有被使用的对象实例将会自动被删除(垃圾回收,GC)。因此,在iterator中不需要与其对应的deleteIterator方法。
四、相关的设计模式
4.1 Visitor模式
Iterator模式是从集合中一个一个取出元素进行遍历,但是并没有在Iterator接口中声明对取出的元素进行何种处理。
Visitor模式则是在遍历元素集合的过程中,对元素进行相同的处理。
在遍历集合的过程中对元素进行固定的处理是常有的需求。Visitor模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是Visitor模式。
4.2 Composite模式
Composite模式是具有递归结构的模式,在其中使用Iterator模式比较困难。
4.3 Factory Method模式
在iterator方法中生成Iterator的实例时可能会使用Factory Method模式。