0%

译-设计模式-行为模式之Iterator

意图

Iterator是行为模式的一种,允许在不暴露底层结构的情况下顺序访问聚合对象中的元素。

问题

集合是编程中最常用的数据结构。它把一组对象放到一个单独的容器中。

大多集合看起来都像元素的列表。然而,一些集合以树、图或者其他复杂数据结构组织数据。并且每个集合都必须提供一个方法让用户可以按照顺序处理集合中的元素。

但是,要怎么顺序遍历一个复杂结构呢?必须有几个方法来做到这一点。比如,今天想要深度优先来遍历树。但是,明天又想宽度优先来遍历。下周,又可能需要随机访问集合元素。

添加越来越多的遍历算法使其丧失了它主要的职责,即高效存储数据。另一方面,一些特定的算法只只在指定的应用场景中适用。

解决

Iterator模式建议抽出集合遍历方法到一个叫做“Iterator”的独立对象中。

迭代器封装遍历细节,像当前位置及还有多少元素需要遍历。允许几个迭代器去独立遍历相同的集合。集合本身甚至不知道有人在访问它的元素。

甚至,如果你需要一个特别的方式来遍历一个集合,你只需要创建一个新的迭代器类而不需要改动集合代码。

真实世界的类比

旅游向导

你计划去罗马一周来参观它所有的景点。但是到那后,你花了很长的时间来找斗兽场。

另一方面,你还有些备用预算可以请当地的导游。这个导游可以带你去看每个景点并且能给你讲许多有趣的故事。

如果预算不够,你可以在手机上使用虚拟导游。它虽然没有真正的导游那么有趣,但是便宜。你可以只去你感兴趣的地方。

真正的导游和虚拟导游都是罗马提供的遍历景点的迭代器。

结构

structor

  1. Iterator定义了遍历集合的接口。通常有获取下/上一个元素方法和用来跟踪是否迭代完毕的方法。

  2. 具体的Iterator实现遍历集合的特定算法。迭代器对象应当自己追踪遍历进度。它允许几个迭代器独立遍历相同的集合。

  3. Collection接口声明了可以被迭代的集合。正如我们之前提到的,不是每一个集合都被表示成一个列表。集合可能把元素存储在一个数据库中,需要通过远程API获取他们;或者存储在一个Composite树中。因此,集合本身可能会创建迭代器,因为有一定数量的迭代器能够处理给定的集合类型。

  4. 具体的Collection在每次客户端请求时都会返回一个新的特定的迭代器实例。注意,这个方法的参数都要返回抽象迭代器类型。这将使得客户端和具体的迭代器独立。

  5. 客户端通过集合和迭代器的通用接口工作。这种方式让客户端与具体的类解耦。它允许添加新的迭代器并且不要修改现有代码。

    通常,客户端自己不创建迭代器,而是从集合对象获取。但是客户端总是可以直接创建迭代器如果它需要特定的迭代器。

伪代码

在这个例子中,迭代器模式被用来遍历封装了访问Facebook社交关系的集合。这个集合提供了几种可以以不同方式遍历配置文件的迭代器。

friend迭代器遍历给定配置集合中所有的朋友。colleague迭代器遍历同样遍历给定配置文件集合中的朋友但跳过了那些不是同事的朋友。所有的迭代器遵循相同的接口,这些接口允许客户端在不深入社交网络细节的情况下获取配置(比如,认证,发送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
90
91
92
93
94
// The collection interface must declare a factory method for producing
// iterators. You can declare several methods if there are different kinds
// of iteration available in your program.
interface SocialNetwork is
method createFriendsIterator(profileId): ProfileIterator
method createCoworkersIterator(profileId): ProfileIterator


// Each concrete collection will be coupled to a set of concrete iterator
// classes it returns. But the client will not be, since the signature of these
// methods returns iterator interfaces.
class Facebook implements SocialNetwork is
// ... The bulk of the collection's code should go here ...

// Iterator creation code.
method createFriendsIterator(profileId) is
return new FacebookIterator(this, profileId, "friends")
method createCoworkersIterator(profileId) is
return new FacebookIterator(this, profileId, "coworkers")


// The common interface for all iterators.
interface ProfileIterator is
method getNext(): Profile
method hasMore(): bool


// The concrete iterator class.
class FacebookIterator implements ProfileIterator is
// The iterator needs a reference to the collection that it traverses.
private field facebook: Facebook
private field profileId, type: string

// An iterator object traverses collection independently from other
// iterators. Therefore it has to store the iteration state.
private field currentPosition
private field cache: array of Profile

constructor FacebookIterator(facebook, profileId, type) is
this.facebook = network
this.profileId = profileId
this.type = type

private method lazyInit() is
if (cache == null)
cache = facebook.sendSophisticatedSocialGraphRequest(profileId, type)

// Each concrete iterator class has its own implementation of the common
// iterator interface.
method getNext() is
if (hasMore())
currentPosition++
return cache[currentPosition]

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


// Here is another useful trick: you can pass an iterator to a client class,
// instead of giving it access to a whole collection. This way, you do not
// expose the collection to the client.
//
// But there is another benefit: you can change the way the client works with
// the collection at run time by passing it a different iterator. This is
// possible because the client code is not coupled to concrete iterator classes.
class SocialSpammer is
method send(iterator: ProfileIterator, message: string) is
while (iterator.hasNext())
profile = iterator.getNext()
System.sendEmail(profile.getEmail(), message)


// The application class configures collections and iterators and then passes
// them to the client code.
class Application is
field network: SocialNetwork
field spammer: SocialSpammer

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

method sendSpamToFriends(profile) is
iterator = network.createFriendsIterator(profile.getId())
spammer.send(iterator, "Very important message")

method sendSpamToCoworkers(profile) is
iterator = network.createCoworkersIterator(profile.getId())
spammer.send(iterator, "Very important message")

适用性

  • 当你有一个复杂的数据结构,并且你想对客户端隐藏它的复杂性(为了安全或者方便)。

    迭代器封装与复杂数据结构的交互并保护它免受粗心和恶意行为。迭代器模式允许客户端和集合元素协作而不必暴露集合对象。

  • 当你需要几种不同的遍历数据结构的方式,但是你不能把它加到集合中或者这个代码和业务逻辑关联。

    遍历数据结构的算法可能很大。把它放在集合或者主业务逻辑代码中,导致原有代码责任不明确且难以维护。把这些代码放在迭代器中让应用代码简洁和清晰。

  • 当你想要一个单独接口来遍历不同的数据结构。

    迭代器为所有实现者提供了通用的接口,允许在客户端代码中更换不同的迭代器。

如何实现

  1. 定义Iterator接口。它至少包含用来获取容器中下一个元素的方法。方便起见,它还可以包含获取上一个元素,追踪当前位置,检查是否到达迭代器尾部等方法。

  2. 创建Collection接口,它要提供获取一个新迭代器的方法。注意,它应当返回抽象迭代器类型。

  3. 为一个可迭代的集合实现一个具体的迭代器类。一个迭代器只能关联一个集合实例。通常通过迭代器的构造方法产生关联。

  4. 在相应的集合类中实现Collection接口。他们应当创建并返回一个适当的迭代器实例。集合通过迭代器的构造方法和迭代器建立关联。

  5. 在使用了这个模式后,在集合和客户端代码中应该不会再有遍历的代码。客户端每次需要迭代集合元素时都要通过集合获取新的迭代器。

优点

  • 简化集合代码。

  • 提供不同的方式遍历相同的数据结构。

  • 允许并行遍历相同的集合。

缺点

  • 对于使用简单集合的程序来说,有些导致矫枉过正。

和其他模式的关系

  • Iterator可以用来遍历Composite树。

  • Factory Method可以和Iterator一起使用,让集合的子类返回合适的迭代器。

  • Memento可以和Iterator一起使用来捕捉迭代器的当前状态,后面如果需要可以进行回滚。

  • Visitor可以和Iterator一起使用来遍历一个复杂的数据结构,并在这些元素上执行一些操作,即使他们类型不同。

小结

Iterator可以在不暴露复杂数据结构的前提下让客户端顺序访问其元素。

Iterator模式在Java代码中很常见。很多框架和库都提供一种标准的方式来遍历他们的集合。

Java核心库中的java.util.Iterator和java.util.Enumeration是很好的例子。

参考

翻译整理自:https://refactoring.guru/design-patterns/iterator