迭代器 Iterator

迭代器模式是一种行为设计模式,让你能在不暴露集合底层表现形式(列表、 栈和树等)的情况下遍历集合中所有的元素。


为什么要使用?

迭代器模式的对象职责:

在不暴露对象内部细节的情况下,提供对容器对象中各个元素进行访问的方法。

👉 减少程序中重复的遍历代码。

🎈 使用迭代器模式是将遍历算法作为容器对象自身的一种“属性方法”来使用,能够有效地避免写很多重复的代码,不会暴露其内部结构。

👉 隐藏统一遍历集合的方法逻辑。

🎈 迭代器模式把对不同集合类的访问逻辑抽象出来,在不同暴露集合内部结构的情况下,隐藏不同集合遍历需要使用的算法,同时能够对外提供更为简便的访问算法接口。


模式结构

  • 迭代器(Iterator)接口声明了遍历集合所需的操作:获取下一个元素、获取当前位置和重新开始迭代等。
  • 具体迭代器(Concrete Iterators)实现遍历集合的一种特定算法。迭代器对象必须跟踪自身遍历的进度。这使得多个迭代器可以相互独立地遍历同一集合。
  • 集合(Collection)接口声明一个或多个方法来获取与集合兼容的迭代器。请注意,返回方法的类型必须被声明为迭代器接口,因此具体集合可以返回各种不同种类的迭代器。
  • 具体集合(Concrete Collections)会在客户端请求迭代器时返回一个特定的具体迭代器类实体。
  • 客户端(Client)通过集合和迭代器的接口与两者进行交互。这样一来客户端无需与具体类进行耦合,允许同一客户端代码使用各种不同的集合和迭代器。客户端通常不会自行创建迭代器,而是会从集合中获取。但在特定情况下,客户端可以直接创建一个迭代器(例如当客户端需要自定义特殊迭代器时)。

迭代器模式的类图:


模式实现

该示例使用迭代器模式为 BookShelf 书架类提供了一个迭代器 BookShelfIterator,实现了遍历书架的功能。

示例程序的类图

代码实现

迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package example;

/** 迭代器接口 */
public interface Iterator {
/**
* 是否存在下一个元素
* @return true存在
*/
boolean hasNext();

/**
* 获取下一个对象
* @return 下一个对象
*/
Object next();
}
具体迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package example;

/** 书架类迭代器具体实现 */
public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index = 0;

public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
}

/**
* 是否存在下一个元素
* @return true存在
*/
@Override
public boolean hasNext() {
if (index >= bookShelf.size())
{
return false;
}
return true;
}

/**
* 获取下一个对象
* @return 下一个对象
*/
@Override
public Object next() {
Book book = bookShelf.get(index);
index++;
return book;
}
}
集合
1
2
3
4
5
6
7
8
9
10
package example;

/** 集合接口 */
public interface Aggregate {
/**
* 获取集合的迭代器
* @return 迭代器
*/
Iterator iterator();
}
具体集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package example;

/** 书架类具体集合实现 */
public class BookShelf implements Aggregate {
/** 存储的书籍信息 */
private Book[] books;
/** 书籍数量(当前书籍数组中最后一本书籍的下标) */
private int last = 0;
/** 书籍数组能容纳数量 */
private int size;

public BookShelf(int maxSize) {
this.size = maxSize;
this.books = new Book[maxSize];
}

/**
* 根据下标获取书籍
* @param index 下标
* @return 指定下标书籍
*/
public Book get(int index) {
if (index >= 0 && index < size) {
return books[index];
}
return null;
}

/**
* 在最后一本书籍上添加
* @param book 需要添加的书籍
*/
public void append(Book book) {
// 扩充
if (last >= size) {
this.size = 2 * this.size;
Book[] expBooks = new Book[this.size];
for (int i = 0; i < this.books.length; i++) {
expBooks[i] = this.books[i];
}
this.books = expBooks;
}
books[last] = book;
last++;
}

/**
* 获取当前书架的书籍数量
* @return
*/
public int size() {
return last;
}

/**
* 获取集合的迭代器
* @return 迭代器
*/
@Override
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package example;

/** 书籍类 */
public class Book {
private String name;

public Book(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import example.Book;
import example.BookShelf;
import example.Iterator;

/** 测试迭代器模式 */
public class Test {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(2);
bookShelf.append(new Book("设计模式"));
bookShelf.append(new Book("数据结构"));
// BookShelf的append方法会自动扩充。
bookShelf.append(new Book("软件工程"));
bookShelf.append(new Book("软件建模"));
Iterator it = bookShelf.iterator();
int stop = 0;
while (it.hasNext()) {
stop++;
Book book = (Book) it.next();
System.out.println("1: " + book.getName());
if (stop == 2) {
System.out.println("-- 中断遍历,但是迭代器仍保留遍历时中断的下标! --");
break;
}
}
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println("2: " + book.getName());
}
}
}

输出结果

1
2
3
4
5
1: 设计模式
1: 数据结构
-- 中断遍历,但是迭代器仍保留遍历时中断的下标! --
2: 软件工程
2: 软件建模

从测试和结果中可以得出,迭代器模式能让我们暂停遍历并在需要时继续。


常用场景和解决方案

  • 出于便利性或安全性等原因,希望对客户端隐藏其遍历算法复杂性时。
  • 需要简化重复的循环遍历逻辑时,使用该模式可以减少程序中重复的遍历代码。
  • 如果你希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。该模式为集合和迭代器提供了一些通用接口。如果你在代码中使用了这些接口,那么将其他实现了这些接口的集合和迭代器传递给它时,它仍将可以正常运行。

模式的优缺点

优点 缺点
单一职责原则。通过将体积庞大的遍历算法代码抽取为独立的类,你可对客户端代码和集合进行整理。 如果你的程序只与简单的集合进行交互,应用该模式可能会矫枉过正。
开闭原则。你可实现新型的集合和迭代器并将其传递给现有代码,无需修改现有代码。 对于某些特殊集合,使用迭代器可能比直接遍历的效率低。
你可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态。 增加子类数量或系统复杂性。
相似的,你可以暂停遍历并在需要时继续。

拓展知识

  • 你可以使用迭代器模式来遍历组合模式树。
  • 你可以同时使用工厂方法模式和迭代器来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
  • 你可以同时使用备忘录模式和迭代器来获取当前迭代器的状态,并且在需要的时候进行回滚。
  • 可以同时使用访问者模式和迭代器来遍历复杂数据结构,并对其中的元素执行所需操作,即使这些元素所属的类完全不同。


🔙 设计模式

📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 设计模式 –refactoringguru

▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育

📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1