--- title: 设计模式之迭代器模式 date: 2015-01-19 17:20:00 order: 16 categories: - 设计 - 设计模式 tags: - 设计 - 设计模式 permalink: /pages/69095ee8/ --- # 设计模式之迭代器模式 ## 意图 **迭代器模式**(Iterator) 是一种行为设计模式, 让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素。 ## 适用场景 - 当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式。 - 使用该模式可以减少程序中重复的遍历代码。 - 如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式。 ## 结构 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519153411.png) ### 结构说明 1. **迭代器** (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。 2. **具体迭代器** (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。 3. **集合** (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。 4. **具体集合** (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。 5. **客户端** (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。 - 客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。 ### 结构代码范式 **Iterator** : 定义访问元素的接口。 ```java interface Iterator { public Object first(); public Object next(); public boolean isDone(); public Object currentItem(); } ``` **ConcreteIterator** : 实现 Iterator 接口。记录当前访问的元素在集合中的位置信息。 ```java 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 对象的接口。 ```java interface Aggregate { public Iterator CreateIterator(); } ``` **ConcreteAggregate** : 实现 Iterator 接口,返回一个合适的 ConcreteIterator 实例。 ```java class ConcreteAggregate implements Aggregate { private List items = new ArrayList(); @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); } } ``` 客户端 ```java 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(); } } } ``` 输出 ``` 第一个人是:张三 所有人的名单是: 张三 李四 王五 赵六 ``` ## 伪代码 在本例中, **迭代器**模式用于遍历一个封装了访问微信好友关系功能的特殊集合。 该集合提供使用不同方式遍历档案资料的多个迭代器。 ![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20210519153656.png) “好友 (friends)” 迭代器可用于遍历指定档案的好友。 “同事 (colleagues)” 迭代器也提供同样的功能, 但仅包括与目标用户在同一家公司工作的好友。 这两个迭代器都实现了同一个通用接口, 客户端能在不了解认证和发送 REST 请求等实现细节的情况下获取档案。 客户端仅通过接口与集合和迭代器交互, 也就不会同具体类耦合。 如果你决定将应用连接到全新的社交网络, 只需提供新的集合和迭代器类即可, 无需修改现有代码。 ```java // 集合接口必须声明一个用于生成迭代器的工厂方法。如果程序中有不同类型的迭 // 代器,你也可以声明多个方法。 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, "非常重要的消息") ``` ## 与其他模式的关系 - 你可以使用[迭代器模式](https://refactoringguru.cn/design-patterns/iterator)来遍历[组合模式](https://refactoringguru.cn/design-patterns/composite)树。 - 你可以同时使用[工厂方法模式](https://refactoringguru.cn/design-patterns/factory-method)和[迭代器](https://refactoringguru.cn/design-patterns/iterator)来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。 - 你可以同时使用[备忘录模式](https://refactoringguru.cn/design-patterns/memento)和[迭代器](https://refactoringguru.cn/design-patterns/iterator)来获取当前迭代器的状态, 并且在需要的时候进行回滚。 - 可以同时使用[访问者模式](https://refactoringguru.cn/design-patterns/visitor)和[迭代器](https://refactoringguru.cn/design-patterns/iterator)来遍历复杂数据结构, 并对其中的元素执行所需操作, 即使这些元素所属的类完全不同。 ## 案例 **使用示例:** 该模式在 Java 代码中很常见。 许多框架和程序库都会使用它来提供遍历其集合的标准方式。 下面是该模式在核心 Java 程序库中的一些示例: - [`java.util.Iterator`](http://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html)的所有实现 (还有 [`java.util.Scanner`](http://docs.oracle.com/javase/8/docs/api/java/util/Scanner.html))。 - [`java.util.Enumeration`](http://docs.oracle.com/javase/8/docs/api/java/util/Enumeration.html)的所有实现 **识别方法:** 迭代器可以通过导航方法 (例如 `next`和 `previous`等) 来轻松识别。 使用迭代器的客户端代码可能没有其所遍历的集合的直接访问权限。 ## 参考资料 - [《Head First 设计模式》](https://book.douban.com/subject/2243615/) - [《大话设计模式》](https://book.douban.com/subject/2334288/) - [设计模式教程](https://refactoringguru.cn/design-patterns/catalog)