Iterator (迭代器)模式
1.1 Iterator 模式
Iterator 模式用于在数据集合中按照顺序遍历集合。单词 Iterate 有反复做某件事情的医生,汉语称为“迭代器”。
1.2 示例程序
作用:将书放置到书架(BookShelf)上,并将书的名字按顺序显示出来。
|| Aggregate 接口
Aggregate 接口是索要遍历的集合的接口。实现了该接口的类将成为一个可以保存多个元素的集合,像数组一样。Aggregate 有“使聚集”、“集合”的意思。
/**
*
* describe 生成一个用于遍历集合的迭代器
* @author xmc
* @date 2019/3/19 15:11
*/
public interface Aggregate<T> {
Iterator<T> iterator();
}
在接口中声明的方法只有一个 iterator 方法,该方法会生成一个用于遍历集合的迭代器。
|| Iterator 接口
Iterator 接口用于遍历集合中的元素,其作用相当于循环语句中的循环变量。下面定义了最简单的 Iterator 接口。
/**
*
* describe 最简单的 Iterator 接口
*/
public interface Iterator<T> {
/**
*
* describe 判断是否存在下一个元素
* @return boolean
*/
boolean hasNext();
/**
*
* describe 返回下一个元素的方法
*/
T next();
}
声明两个方法,判断是否存在下一个元素的 hasNext 方法,和获取下一个元素的 next 方法。
haxNext 方法返回值是 boolean 类型,当集合中存在下一个元素时,返回 true,反正不存在,返回 false,即已经遍历至集合末尾。主要用于循环终止条件。
next 方法返回的是一个泛型元素,同时还隐含着将迭代器移动至下一个元素的处理。因为 Iterator 接口只知道方法名,具体的还是需要查看 Iterator 实现类中的处理。
|| Book 类
Book 类是表示书的类,做的事情只有一件-通过 getName 方法获取书的名字。
/**
*
* describe 书 对象
* @author xmc
*/
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
|| BookShelf 类
BookShelf 类是表示书架的类。需要将该类作为集合进行处理,因此实现了 Aggregate 接口。且还实现了 Aggregate 接口的 iterator 方法。
/**
*
* describe 书架类
* @author xmc
*/
public class BookShelf implements Aggregate<Book>{
private Book[] books;
private int last = 0;
public BookShelf(int size) {
this.books = new Book[size];
}
public Book getBookAt(int index) {
return books[index];
}
// 书架上添加书
public void appendBook(Book book) {
books[last] = book;
last++;
}
public int getLength() {
return last;
}
// 返回迭代器
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
// 获取迭代器
Iterator<Book> iterator = bookShelf.iterator();
// 如果存在下一个,则会一直循环
while (iterator.hasNext()) {
System.out.println(iterator.next().getName());
}
}
}
书架中定义了 books 字段,它是 Book 类型的数组。在构造器中初始化数组大小。
iterator 方法会生成并返回 BookShelfIterator 类的实例作为 BookShelf 类对应的 Iterator。
|| BookShelfIterator 类
用于遍历书架的 BookShelfIterator 类。
public class BookShelfIterator implements Iterator<Book>{
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
/**
*
* describe 判断是否存在下一个元素
* @return boolean
*/
@Override
public boolean hasNext() {
return (index < bookShelf.getLength());
}
/**
*
* describe 返回下一个元素的方法
* @return T
*/
@Override
public Book next() {
// 取出当前指向的书,同时令 index 指向下一个
Book book = bookShelf.getBookAt(index);
// 让迭代器指向下一本书位置
index++;
return book;
}
}
因为BookShelfIterator 类需要发挥 Iterator 的作用,所以它实现了 Iterator 接口。
bookShefl 字段表示 BookShelfIterator 所要遍历的书架。index 字段表示迭代器当前所指向的书的下标。
hasNext 方法将会判断书架中还有没有下一本书,如果有就返回 true,没有返回 false。可以通过比较 index 和书架中书的总数来判断。
next 方法会返回迭代器当前所指向的书,并让迭代器指向下一本书位置。
|| Main 类
我们使用 Main 类来制作一个小书架。
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
// 获取迭代器
Iterator<Book> iterator = bookShelf.iterator();
// 如果存在下一个,则会一直循环
while (iterator.hasNext()) {
System.out.println(iterator.next().getName());
}
}
}
while 部分就是 it.hasNext(),只要书架上有书,循环就不会停止。
Around the World in 80 Days
Bible
Cinderella
Daddy-Long-Legs
1.3 Iterator 模式中登场的角色
-
Iterator (迭代器)
该角色负责定义按顺序逐个遍历元素的接口(API)。在示例中,由 Iterator 接口扮演这个角色,定义了 hasNext 和 next 方法。其中,hasNext 方法用于判断是否存在下一个元素,next 方法则用于获取该元素。 -
ConcreateIterator(具体的迭代器)
该角色负责实现 Iterator 角色所定义的接口(API)。在示例程序中,由 BookShelfIterator 类扮演在这个角色。该角色中包含了遍历集合所必须的信息。 -
Aggregate (集合)
该角色负责定义创建 Iterator 角色的接口(API)。这个接口是一个方法,会创建出“按顺序访问保存在我内部元素”的对象。在示例程序中,由 Aggregate 接口扮演这个角色,它定义了 iterator 方法。 -
ConcreteAggregate (具体的集合)
该角色负责实现 Aggregate 角色所定义的接口。它会创建出具体的 Iterator 角色,即 ConcreteIterator 角色。在示例程序中,由 BookShelf 类扮演这个角色,它实现了 iterator 方法。
1.4 拓展思路要点
|| 不管实现如何变化,都可以使用 Iterator
为什么一定要引入 Iterator 这个复杂的设计模式?如果是数组,直接 for 循环进行遍历不就可以了么?为什么要在集合之外引入 Iterator 这个角色呢?
一个重要的原因,引入 Iterator 后可以将遍历与实现分离出来。
while (iterator.hasNext()) {
System.out.println(iterator.next().getName());
}
这里只使用了 Iterator 的 hasNext 和 next 方法,并没有调用 BookShelf 的方法。也就是说,这里的 While 循环并不依赖于 BookShelf 的实现。
如果编写 BookShelf 的开发人员决定放弃用数组来管理书本,而使用 ArrayList 会怎么样?不管 BookShelf 如何变化,只要 BookShelf 的 iterator 方法能正确的返回 Iterator 实例,即使不对 while 循环做修改,代码都可以正常工作。这对 BookShelf 的调用者来说真是太方便了。
设计模式的作用就是帮助我们编写可复用的类。所谓的“可复用”,就是指将类实现为“组件”,当一个组件发生改变时,不需要对其他的组件进行修改或是只需要很小的修改即可应对。
这样也就能理解为什么示例程序中 iterator 方法的返回值不是 BookSheflIterator 类型而是 Iterator 类型了。这段程序就是要使用 Iterator 的方法进行编程,而不是 BookShelfIterator 的方法。
|| 难以理解抽象类和接口
难以理解抽象类和接口的人常使用 ConcreteAggregate 角色和 ConcreteIterator 角色编程,而不使用 Aggregate 接口和 Iterator 接口,总想用具体的类来解决所有问题。
但是如果只使用具体的类来解决问题,很容易导致类之间的强耦合,这些类也难以作为组件被再次利用。为了弱化类之间的耦合,进而使得类更加容易作为组件被再次利用,我们需要引入抽象类和接口。
“不要只使用具体的类来编程,要优先使用抽象类和接口来编程”。
|| Aggregate 和 Iterator 的对应
BookShelfIterator 类知道 BookShelf 类是如何实现的。这样,我们才知道如何来获取下一本数的 getBookAt 方法。也就是说,如果BookShelf 的实现发生了改变,即 getBookAt 方法这个接口(API)发生变化时,我们必须修改 BookShelfIterator 类。
正如 Aggregate 和 Iterator 这两个接口是对应的一样,ConcreteAggregate 和 ConcreteIterator 这两个类也是对应的。
|| 多种 Iterator
“将遍历功能置于 Aggregate 角色之外” 是 Iterator 模式的一个特征,可以根据一个 ConcreteAggregate 角色编写多个 ConcreteIterator 角色。
在示例程序中展示的 Iterator 类只是简答地从前向后遍历集合。还可以有其他的遍历方式。
- 从后向前遍历
- 既可以从前向后遍历,也可以从后向前遍历(next 和 previous 方法)
- 指定下标进行“跳跃式”遍历