设计模式之迭代器模式

设计模式之迭代器模式

意图

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

适用场景

  • 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。
  • 使用该模式可以减少程序中重复的遍历代码。
  • 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。

结构

img

结构说明

  1. 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
  2. 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
  3. 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
  4. 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。
  5. 客户端 (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。
    • 客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。

结构代码范式

Iterator : 定义访问元素的接口。

1
2
3
4
5
6
interface Iterator {
public Object first();
public Object next();
public boolean isDone();
public Object currentItem();
}

ConcreteIterator : 实现 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
class ConcreteIterator implements Iterator {
private int current = 0;
private ConcreteAggregate aggregate;

public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
}

@Override
public Object first() {
return aggregate.get(0);
}

@Override
public Object next() {
current++;
if (current < aggregate.size()) {
return aggregate.get(current);
}
return null;
}

@Override
public boolean isDone() {
return (current >= aggregate.size()) ? true : false;
}

@Override
public Object currentItem() {
return aggregate.get(current);
}
}

Aggregate : 定义创建 Iterator 对象的接口。

1
2
3
interface Aggregate {
public Iterator CreateIterator();
}

ConcreteAggregate : 实现 Iterator 接口,返回一个合适的 ConcreteIterator 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ConcreteAggregate implements Aggregate {
private List<Object> items = new ArrayList<Object>();

@Override
public Iterator CreateIterator() {
return new ConcreteIterator(this);
}

public int size() {
return items.size();
}

public Object get(int index) {
return items.get(index);
}

public void set(int index, Object element) {
items.set(index, element);
}

public void add(Object element) {
items.add(element);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class IteratorPattern {
public static void main(String[] args) {
ConcreteAggregate aggregate = new ConcreteAggregate();
aggregate.add("张三");
aggregate.add("李四");
aggregate.add("王五");
aggregate.add("赵六");

Iterator iter = new ConcreteIterator(aggregate);
Object item = iter.first();
System.out.println("第一个人是:" + item);
System.out.println("所有人的名单是:");
while (!iter.isDone()) {
System.out.println(iter.currentItem());
iter.next();
}
}
}

输出

1
2
3
4
5
6
第一个人是:张三
所有人的名单是:
张三
李四
王五
赵六

伪代码

在本例中, 迭代器模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。

img

“好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。

客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 集合接口必须声明一个用于生成迭代器的工厂方法。如果程序中有不同类型的迭
// 代器,你也可以声明多个方法。
interface SocialNetwork is
method createFriendsIterator(profileId):ProfileIterator
method createCoworkersIterator(profileId):ProfileIterator


// 每个具体集合都与其返回的一组具体迭代器相耦合。但客户并不是这样的,因为
// 这些方法的签名将会返回迭代器接口。
class WeChat implements SocialNetwork is
// ...大量的集合代码应该放在这里...

// 迭代器创建代码。
method createFriendsIterator(profileId) is
return new WeChatIterator(this, profileId, "friends")
method createCoworkersIterator(profileId) is
return new WeChatIterator(this, profileId, "coworkers")


// 所有迭代器的通用接口。
interface ProfileIterator is
method getNext():Profile
method hasMore():bool


// 具体迭代器类。
class WeChatIterator implements ProfileIterator is
// 迭代器需要一个指向其遍历集合的引用。
private field weChat: WeChat
private field profileId, type: string

// 迭代器对象会独立于其他迭代器来对集合进行遍历。因此它必须保存迭代器
// 的状态。
private field currentPosition
private field cache: array of Profile

constructor WeChatIterator(weChat, profileId, type) is
this.weChat = weChat
this.profileId = profileId
this.type = type

private method lazyInit() is
if (cache == null)
cache = weChat.socialGraphRequest(profileId, type)

// 每个具体迭代器类都会自行实现通用迭代器接口。
method getNext() is
if (hasMore())
currentPosition++
return cache[currentPosition]

method hasMore() is
lazyInit()
return currentPosition < cache.length


// 这里还有一个有用的绝招:你可将迭代器传递给客户端类,无需让其拥有访问整
// 个集合的权限。这样一来,你就无需将集合暴露给客户端了。
//
// 还有另一个好处:你可在运行时将不同的迭代器传递给客户端,从而改变客户端
// 与集合互动的方式。这一方法可行的原因是客户端代码并没有和具体迭代器类相
// 耦合。
class SocialSpammer is
method send(iterator: ProfileIterator, message: string) is
while (iterator.hasMore())
profile = iterator.getNext()
System.sendEmail(profile.getEmail(), message)


// 应用程序(Application)类可对集合和迭代器进行配置,然后将其传递给客户
// 端代码。
class Application is
field network: SocialNetwork
field spammer: SocialSpammer

method config() is
if working with WeChat
this.network = new WeChat()
if working with LinkedIn
this.network = new LinkedIn()
this.spammer = new SocialSpammer()

method sendSpamToFriends(profile) is
iterator = network.createFriendsIterator(profile.getId())
spammer.send(iterator, "非常重要的消息")

method sendSpamToCoworkers(profile) is
iterator = network.createCoworkersIterator(profile.getId())
spammer.send(iterator, "非常重要的消息")

与其他模式的关系

案例

使用示例: 该模式在 Java 代码中很常见。 许多框架和程序库都会使用它来提供遍历其集合的标准方式。

下面是该模式在核心 Java 程序库中的一些示例:

识别方法: 迭代器可以通过导航方法 (例如 nextprevious等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。

参考资料