设计模式之迭代器模式
...大约 7 分钟
设计模式之迭代器模式
意图
迭代器模式(Iterator) 是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。
适用场景
- 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。
- 使用该模式可以减少程序中重复的遍历代码。
- 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。
结构
结构说明
- 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
- 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
- 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
- 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。
- 客户端 (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。
- 客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。
结构代码范式
Iterator : 定义访问元素的接口。
interface Iterator {
public Object first();
public Object next();
public boolean isDone();
public Object currentItem();
}
ConcreteIterator : 实现 Iterator 接口。记录当前访问的元素在集合中的位置信息。
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 对象的接口。
interface Aggregate {
public Iterator CreateIterator();
}
ConcreteAggregate : 实现 Iterator 接口,返回一个合适的 ConcreteIterator 实例。
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);
}
}
客户端
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();
}
}
}
输出
第一个人是:张三
所有人的名单是:
张三
李四
王五
赵六
伪代码
在本例中, 迭代器模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。
“好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。
客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。
// 集合接口必须声明一个用于生成迭代器的工厂方法。如果程序中有不同类型的迭
// 代器,你也可以声明多个方法。
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 程序库中的一些示例:
java.util.Iterator
的所有实现 (还有java.util.Scanner
)。java.util.Enumeration
的所有实现
识别方法: 迭代器可以通过导航方法 (例如 next
和 previous
等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。
参考资料
Powered by Waline v2.15.7