圣诞大减价开始了!

迭代器

意图

迭代器是一种行为设计模式,允许您遍历集合的元素,而不暴露其底层表示(列表、堆栈、树等)。

迭代器设计模式

问题

集合是编程中最常用的数据类型之一。尽管如此,集合只是一组对象的容器。

各种类型的集合

各种类型的集合。

大多数集合将它们的元素存储在简单的列表中。然而,其中一些是基于堆栈、树、图和其他复杂的数据结构。

但无论集合是如何结构的,它必须提供某种访问其元素的方式,以便其他代码可以使用这些元素。应该有一种方法可以遍历集合中的每个元素,而不必反复访问相同的元素。

如果您有一个基于列表的集合,这听起来可能是一个简单的工作。你只需要遍历所有的元素。但是如何按顺序遍历复杂数据结构(比如树)中的元素呢?例如,有一天您可能对树的深度优先遍历很满意。然而,第二天您可能需要宽度优先遍历。下一周,你可能需要其他东西,比如随机访问树元素。

各种遍历算法

可以以几种不同的方式遍历同一个集合。

向集合中添加越来越多的遍历算法逐渐模糊了它的主要职责,即有效的数据存储。此外,有些算法可能是为特定的应用程序量身定制的,因此将它们包含到泛型集合类中会很奇怪。

另一方面,应该使用各种集合的客户端代码甚至可能不关心它们如何存储元素。但是,由于集合都提供了不同的访问元素的方式,因此除了将代码与特定的集合类耦合之外,您别无选择。

解决方案

迭代器模式的主要思想是将集合的遍历行为提取到称为对象的单独对象中迭代器

迭代器实现各种遍历算法

迭代器实现各种遍历算法。多个迭代器对象可以同时遍历同一个集合。

除了实现算法本身之外,迭代器对象还封装了遍历的所有细节,比如当前位置以及到结束时还剩下多少元素。因此,几个迭代器可以同时独立地遍历同一个集合。

通常,迭代器提供一种获取集合元素的主要方法。客户端可以一直运行这个方法,直到它不返回任何东西为止,这意味着迭代器已经遍历了所有的元素。

所有迭代器必须实现相同的接口。这使得客户端代码兼容任何集合类型或任何遍历算法,只要有一个合适的迭代器。如果需要一种特殊的方法来遍历一个集合,只需创建一个新的迭代器类,而不必更改集合或客户端。

真实的模拟

在罗马有各种各样的游览方式

在罗马有各种各样的游览方式。

你计划去罗马旅游几天,参观罗马所有的主要景点和景点。但一旦到了那里,你可能会浪费很多时间在原地转圈,甚至找不到罗马斗兽场。

另一方面,你可以为你的智能手机购买一个虚拟指南应用程序,并使用它来导航。它既聪明又便宜,而且你可以在一些有趣的地方待多久就待多久。

第三种选择是,你可以花一部分旅行预算,雇一个对这个城市了如指掌的当地导游。导游会根据你的喜好安排行程,向你展示每一个景点,并告诉你许多令人兴奋的故事。那就更有趣了;但是,唉,也更贵了。

所有这些选择——你脑海中产生的随机方向、智能手机导航仪或人工导航仪——都是罗马众多景点和景点的迭代器。

结构

迭代器设计模式的结构 迭代器设计模式的结构
  1. 迭代器接口声明遍历集合所需的操作:获取下一个元素、检索当前位置、重新启动迭代等。

  2. 具体的迭代器实现用于遍历集合的特定算法。迭代器对象应该单独跟踪遍历进程。这允许几个迭代器彼此独立地遍历同一个集合。

  3. 集合接口声明一个或多个方法,用于获取与集合兼容的迭代器。注意,方法的返回类型必须声明为迭代器接口,以便具体的集合可以返回各种类型的迭代器。

  4. 具体的集合每次客户端请求一个特定的具体迭代器类的新实例时返回一个新实例。您可能想知道,集合的其余代码在哪里?别担心,应该是在同一个班。只是这些细节对于实际的模式并不重要,所以我们省略了它们。

  5. 客户端通过集合和迭代器的接口使用它们。通过这种方式,客户端不会与具体类耦合,允许您使用不同的集合和迭代器与相同的客户端代码。

    通常,客户端不会自己创建迭代器,而是从集合中获取迭代器。然而,在某些情况下,客户端可以直接创建一个;例如,当客户端定义自己的特殊迭代器时。

伪代码

在本例中,迭代器模式用于遍历一种特殊的集合,该集合封装了对Facebook社交图的访问。该集合提供了几个迭代器,可以以各种方式遍历概要文件。

结构的Iterator模式示例

迭代社会档案的例子。

' friends '迭代器可用于遍历给定配置文件的好友。“同事”迭代器做同样的事情,只是它忽略了与目标人员不在同一家公司工作的朋友。这两个迭代器都实现了一个公共接口,该接口允许客户端获取概要文件,而无需深入验证和发送REST请求等实现细节。

客户端代码没有耦合到具体的类,因为它只通过接口与集合和迭代器一起工作。如果你决定将你的应用连接到一个新的社交网络,你只需要提供新的集合和迭代器类,而不需要改变现有的代码。

//收集接口必须声明一个工厂方法,用于产生迭代器。如果你的程序中有不同类型的迭代可用,你可以声明几个方法。interface SocialNetwork is方法createFriendsIterator(profileId):ProfileIterator方法createCoworkersIterator(profileId):ProfileIterator //每个具体的集合都耦合到它返回的一组具体的迭代器类。但是客户端不是,因为这些方法的//签名返回迭代器接口。类Facebook实现SocialNetwork是//…集合的大部分代码应该放在这里……//迭代器创建代码。method createCoworkersIterator(profileId) is return new FacebookIterator(this, profileId, "friends") //所有迭代器的公共接口。Profile method hasMore():bool //具体的迭代器类。迭代器需要一个它所遍历的集合的引用。 private field facebook: Facebook private field profileId, type: string // An iterator object traverses the 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 = facebook this.profileId = profileId this.type = type private method lazyInit() is if (cache == null) cache = facebook.socialGraphRequest(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 currentPosition < cache.length // 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 don't expose the collection to the // client. // // And there's another benefit: you can change the way the // client works with the collection at runtime by passing it a // different iterator. This is possible because the client code // isn't coupled to concrete iterator classes. class SocialSpammer is method send(iterator: ProfileIterator, message: string) is while (iterator.hasMore()) 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")

适用性

当您的集合具有复杂的数据结构,但您希望对客户端隐藏其复杂性时(出于方便或安全原因),请使用Iterator模式。

迭代器封装了处理复杂数据结构的细节,为客户端提供了访问集合元素的几种简单方法。虽然这种方法对客户端非常方便,但它也可以保护集合免受粗心或恶意操作的影响,如果直接使用集合,客户端可能会执行这些操作。

使用该模式可以减少应用程序中遍历代码的重复。

非平凡迭代算法的代码往往非常庞大。当它被置于应用程序的业务逻辑中时,它可能会模糊原始代码的职责,并使其难以维护。将遍历代码移动到指定的迭代器可以帮助您使应用程序的代码更加精简和干净。

当您希望代码能够遍历不同的数据结构或这些结构的类型事先未知时,请使用Iterator。

该模式为集合和迭代器提供了两个通用接口。假设您的代码现在使用这些接口,如果您传递给它实现这些接口的各种集合和迭代器,它仍然可以工作。

如何实施

  1. 声明迭代器接口。至少,它必须有一个从集合中获取下一个元素的方法。但是为了方便起见,您可以添加一些其他方法,例如获取前一个元素、跟踪当前位置以及检查迭代的结束。

  2. 声明集合接口并描述获取迭代器的方法。返回类型应该等于迭代器接口的返回类型。如果你计划有几组不同的迭代器,你可以声明相似的方法。

  3. 为希望可通过迭代器遍历的集合实现具体的迭代器类。迭代器对象必须与单个集合实例链接。通常,这个链接是通过迭代器的构造函数建立的。

  4. 在集合类中实现集合接口。其主要思想是为客户端提供创建迭代器的快捷方式,为特定的集合类量身定制。集合对象必须将自身传递给迭代器的构造函数,以便在它们之间建立链接。

  5. 检查客户端代码,用迭代器替换所有集合遍历代码。客户端每次需要遍历集合元素时,都会获取一个新的迭代器对象。

利与弊

  • 单一责任原则.通过将庞大的遍历算法提取到单独的类中,可以清理客户机代码和集合。
  • 打开/关闭原则.您可以实现新类型的集合和迭代器,并将它们传递给现有代码而不会破坏任何东西。
  • 您可以并行遍历同一个集合,因为每个迭代器对象都包含自己的迭代状态。
  • 出于同样的原因,您可以延迟迭代,并在需要时继续进行。
  • 如果你的应用程序只处理简单的集合,那么应用这个模式可能会有点过头。
  • 使用迭代器可能比直接遍历某些专用集合的元素效率低。

与其他模式的关系

  • 你可以使用迭代器遍历复合树。

  • 你可以使用工厂方法随着迭代器让集合子类返回与集合兼容的不同类型的迭代器。

  • 你可以使用纪念品随着迭代器捕获当前迭代状态,并在必要时将其回滚。

  • 你可以使用游客随着迭代器遍历一个复杂的数据结构并对其元素执行一些操作,即使它们都具有不同的类。

代码示例

c#中的迭代器c++中的迭代器Go中的迭代器Java中的迭代器PHP中的迭代器Python中的迭代器Ruby中的迭代器Rust中的迭代器Swift中的迭代器TypeScript中的迭代器

Baidu
map