设计模式-Iterator
本文参(chao)考(xi)《图解设计模式》 结城浩 (作者) 杨文轩 (译者)
1.Iterator 模式
迭代器作用于集合,是用来遍历集合元素的对象。
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器封装了对集合的遍历,使得不用了解集合的内部细节,就可以使用同样的方式遍历不同的集合。
迭代器模式属于行为型模式。
2.示例
首先,让我们来看一段实现了Iterator 模式的示例程序。这段示例程序的作用是将书(Book)放置到书架(BookShelf)中,并将书的名字按顺序显示出来:
Aggregate 接口:
是所要遍历的集合的接口。实现了该接口的类将成为一个可以保存多个元素的集合:
1 public interface Aggregate { 2 public abstract Iterator iterator();//生成一个实现了Iterator接口的类的实例 3 }
Iterator 接口:
Iterator 接口用于遍历集合中的元素,其作用相当于循环语句中的循环变量。最简单的Iterator 接口如下:
1 public interface Iterator { 2 public abstract boolean hasNext(); 3 public abstract Object next(); 4 }
这里我们声明了两个方法:
hasNext()方法:返回值是boolean 类型。其原因很容易理解,当集合中存在下一个元素时,该方法返回true ;当集合中不存在下一个元素,即已经遍历至集合末尾时,该方法返回false。hasNext 方法主要用于循环终止条件。
next()方法:返回类型是Object。这表明该方法返回的是集合中的一个元素。但是,next 方法的作用并非仅仅如此。为了能够在下次调用next 方法时正确地返回下一个元素,该方法中还隐含着将迭代器移动至下一个元素的处理。说“隐含”,是因为Iterator 接口只知道方法名。想要知道next 方法中到底进行了什么样的处理,还需要看一下实现了Iterator 接口的类(BookShelfIterator)。这样,我们才能看懂next 方法的作用。
Book 类:
Book 类是表示书的类。提供getName() 方法获取书名。
1 public class Book { 2 private String name; 3 public Book(String name) { 4 this.name = name; 5 } 6 public String getName() { 7 return name; 8 } 9 }
BookShelf 类:
BookShelf 类是表示书架的类。集合Aggregate的实现类。
1 public class BookShelf implements Aggregate { 2 private Book[] books; 3 private int last = 0; 4 public BookShelf(int maxsize) { 5 this.books = new Book[maxsize]; 6 } 7 public Book getBookAt(int index) { 8 return books[index]; 9 } 10 public void appendBook(Book book) { 11 this.books[last] = book; 12 last++; 13 } 14 public int getLength() { 15 return last; 16 } 17 public Iterator iterator() { 18 return new BookShelfIterator(this); 19 } 20 }
BookShelfIteraotr 类:
Iterator 接口的具体实现方法。
1 public class BookShelfIterator implements Iterator { 2 private BookShelf bookShelf;//遍历对象 3 private int index;//下标 4 public BookShelfIterator(BookShelf bookShelf) { 5 this.bookShelf = bookShelf; 6 this.index = 0; 7 } 8 public boolean hasNext() { 9 if (index < bookShelf.getLength()) { 10 return true; 11 } else { 12 return false; 13 } 14 } 15 public Object next() { 16 Book book = bookShelf.getBookAt(index); 17 index++; 18 return book; 19 } 20 }
构造函数BookShelfIterator():将接收到的BookShelf的实例保存在bookShelf 字段中,并将index 初始化为0。
hasNext()方法:该方法将会判断书架中还有没有下一本书,如果有就返回true,如果没有就返回false。而要知道书架中有没有下一本书,可以通过比较index 和书架中书的总册数(bookShelf.getLength() 的返回值)来判断。
next()方法:会返回迭代器当前所指向的书(Book 的实例),并让迭代器指向下一本书。next()方法稍微有些复杂,它首先取出book 变量作为返回值,然后让index 指向后面一本书。
Main 类:
至此,遍历书架的准备工作就完成了。一下是测试类:
1 public class Main { 2 public static void main(String[] args) { 3 BookShelf bookShelf = new BookShelf(4); 4 bookShelf.appendBook(new Book("Around the World in 80 Days")); 5 bookShelf.appendBook(new Book("Bible")); 6 bookShelf.appendBook(new Book("Cinderella")); 7 bookShelf.appendBook(new Book("Daddy-Long-Legs")); 8 Iterator it = bookShelf.iterator(); 9 while (it.hasNext()) { 10 Book book = (Book)it.next(); 11 System.out.println(book.getName()); 12 } 13 } 14 }
测试输出结果如下:
3.Iterator 模式中的登场角色
通过以上示例,让我们来看看Iterator 模式中的登场角色。
◆ Iterator(迭代器)
该角色负责定义按顺序逐个遍历元素的接口(API)。在示例程序中,由Iterator 接口扮演这个角色,它定义了hasNext 和next 两个方法。其中,hasNext 方法用于判断是否存在下一个元素,next 方法则用于获取该元素。
◆ ConcreteIterator(具体的迭代器)
该角色负责实现Iterator 角色所定义的接口(API)。在示例程序中,由BookShelfIterator 类扮演这个角色。该角色中包含了遍历集合所必需的信息。在示例程序中,BookShelf 类的实例保存在bookShelf 字段中,被指向的书的下标保存在index 字段中。
◆ Aggregate(集合)
该角色负责定义创建Iterator 角色的接口(API)。这个接口(API)是一个方法,会创建出“按顺序访问保存在我内部元素的人”。在示例程序中,由Aggregate 接口扮演这个角色,它里面定义了iterator 方法。
◆ ConcreteAggregate(具体的集合)
该角色负责实现Aggregate 角色所定义的接口(API)。它会创建出具体的Iterator 角色,即ConcreteIterator 角色。在示例程序中,由BookShelf 类扮演这个角色,它实现了iterator 方法。Iterator 模式的类图如下:
4.拓展思路的要点
不管实现如何变化,都可以使用Iterator
为什么一定要考虑引入Iterator 这种复杂的设计模式呢?如果是数组,直接使用for 循环语句进行遍历处理不就可以了吗?为什么要在集合之外引入Iterator 这个角色呢?
一个重要的理由是,引入Iterator 后可以将遍历与实现分离开来。请看下面的代码。
1 while (it.hasNext()) { 2 Book book = (Book)it.next(); 3 System.out.println(book.getName()); 4 }
这里只使用了Iterator 的hasNext 方法和next 方法,并没有调用BookShelf 的方法。也就是说,这里的while 循环并不依赖于BookShelf 的实现。
如果编写BookShelf 的开发人员决定放弃用数组来管理书本,而是用java.util.Vector取而代之,会怎样呢?不管BookShelf 如何变化,只要BookShelf 的iterator 方法能正确地返回Iterator 的实例(也就是说,返回的Iterator 类的实例没有问题,hasNext 和next 方法都可以正常工作),即使不对上面的while 循环做任何修改,代码都可以正常工作。
这对于BookShelf 的调用者来说真是太方便了。设计模式的作用就是帮助我们编写可复用的类。所谓“可复用”,就是指将类实现为“组件”,当一个组件发生改变时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。
这样也就能理解为什么在示例程序中iterator 方法的返回值不是BookShelfIterator 类型而是Iterator 类型了(代码清单1-6)。这表明,这段程序就是要使用Iterator 的方法进行编程,而不是BookShelfIterator 的方法。
难以理解抽象类和接口
难以理解抽象类和接口的人常常使用ConcreteAggregate 角色和ConcreteIterator 角色编程,而不使用Aggregate 接口和Iterator 接口,他们总想用具体的类来解决所有的问题。
但是如果只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。
这也是贯穿本书的思想。即使大家现在无法完全理解,相信随着深入阅读本书,也一定能够逐渐理解。请大家将“不要只使用具体类来编程,要优先使用抽象类和接口来编程”印在脑海中。
Aggregate 和Iterator 的对应
请大家仔细回忆一下我们是如何把BookShelfIterator 类定义为BookShelf 类的ConcreteIterator 角色的。BookShelfIterator 类知道BookShelf 是如何实现的。也正是因为如此,我们才能调用用来获取下一本书的getBookAt 方法。
也就是说,如果BookShelf 的实现发生了改变,即getBookAt 方法这个接口(API)发生变化时,我们必须修改BookShelfIterator 类。
正如Aggregate 和Iterator 这两个接口是对应的一样,ConcreteAggregate 和ConcreteIterator 这两个类也是对应的。
容易弄错“下一个”
在Iterator 模式的实现中,很容易在next 方法上出错。该方法的返回值到底是应该指向当前元素还是当前元素的下一个元素呢?更详细地讲,next 方法的名字应该是下面这样的。
returnCurrentElementAndAdvanceToNextPosition
也就是说,next 方法是“返回当前的元素,并指向下一个元素”。
还容易弄错“ 最后一个”
在Iterator 模式中,不仅容易弄错“下一个”,还容易弄错“最后一个”。hasNext 方法在返回最后一个元素前会返回true,当返回了最后一个元素后则返回false。稍不注意,就会无法正确地返回“最后一个”元素。
请大家将hasNext 方法理解成“确认接下来是否可以调用next 方法”的方法就可以了。
多个Iterator
“将遍历功能置于Aggregate 角色之外”是Iterator 模式的一个特征。根据这个特征,可以针对一个ConcreteAggregate 角色编写多个ConcreteIterator 角色。
迭代器的种类多种多样
在示例程序中展示的Iterator 类只是很简单地从前向后遍历集合。其实,遍历的方法是多种多样的。
● 从最后开始向前遍历
● 既可以从前向后遍历,也可以从后向前遍历(既有next方法也有previous方法)
● 指定下标进行“跳跃式”遍历
学到这里,相信大家应该可以根据需求编写出各种各样的Iterator 类了。
不需要deleteIterator
在Java 中,没有被使用的对象实例将会自动被删除(垃圾回收,GC)。因此,在iterator 中不需要与其对应的deleteIterator 方法。
5.相关的设计模式
◆ Visitor 模式
Iterator 模式是从集合中一个一个取出元素进行遍历,但是并没有在Iterator 接口中声明对取出的元素进行何种处理。
Visitor 模式则是在遍历元素集合的过程中,对元素进行相同的处理。
在遍历集合的过程中对元素进行固定的处理是常有的需求。Visitor 模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是Visitor 模式。
◆ Composite 模式
Composite 模式是具有递归结构的模式,在其中使用Iterator 模式比较困难。
◆ Factory Method 模式
在iterator 方法中生成Iterator 的实例时可能会使用Factory Method 模式。