迭代器形式

Iterator

迭代器形式是一种行为规划形式, 它能够在不暴露调集底层表现形式 (数组、列表、 栈和树等) 的情况下遍历调集中一切的元素。

遇到的问题

调集是编程中最常运用的数据类型之一。 尽管如此, 调集仅仅一组方针的容器而已。

2023 跟我一起学习设计模式:  迭代器模式

大部分调集运用简单列表存储元素。 但有些调集还会运用栈、 树、 图和其他杂乱的数据结构。

不管调集的构成办法怎么, 它都有必要供给某种拜访元素的办法, 便于其他代码运用其间的元素。 调集应供给一种能够遍历元素的办法, 且保证它不会循环往复地拜访同一个元素。

假如你的调集根据列表, 那么这项作业听上去似乎很简单。 但怎么遍历杂乱数据结构 (例如树) 中的元素呢? 例如, 今天你需求运用深度优先算法来遍历树结构, 明天可能会需求广度优先算法; 下周则可能会需求其他办法 (比如随机存取树中的元素)。

2023 跟我一起学习设计模式:  迭代器模式

可经过不同的办法遍历相同的调集。

不断向调集中添加遍历算法会含糊其 “高效存储数据” 的首要责任。 此外, 有些算法可能是根据特定运用订制的, 将其参加泛型调集类中会显得十分奇怪。

另一方面, 运用多种调集的客户端代码可能并不关心存储数据的办法。 不过因为调集供给不同的元素拜访办法, 你的代码将不得不与特定调集类进行耦合。

解决办法

迭代器形式的首要思维是将调集的遍历行为抽取为单独的迭代器方针。

2023 跟我一起学习设计模式:  迭代器模式

迭代器可完成多种遍历算法。 多个迭代器方针可一起遍历同一个调集。

除完成本身算法外, 迭代器还封装了遍历操作的一切细节, 例如当时方位和末尾剩下元素的数量。 因而, 多个迭代器能够在彼此独立的情况下一起拜访调集。

迭代器一般会供给一个获取调集元素的基本办法。 客户端可不断调用该办法直至它不回来任何内容, 这意味着迭代器已经遍历了一切元素。

一切迭代器有必要完成相同的接口。 这样一来, 只要有适宜的迭代器, 客户端代码就能兼容任何类型的调集或遍历算法。 假如你需求选用特别办法来遍历调集, 只需创立一个新的迭代器类即可, 无需对调集或客户端进行修正。

迭代器形式结构

2023 跟我一起学习设计模式:  迭代器模式

  1. 迭代器 (Iterator) 接口声明晰遍历调集所需的操作: 获取下一个元素、 获取当时方位和重新开始迭代等。

  2. 详细迭代器 (Concrete Iterators) 完成遍历调集的一种特定算法。 迭代器方针有必要跟踪本身遍历的进展。 这使得多个迭代器能够彼此独登时遍历同一调集。

  3. 调集 (Collection) 接口声明一个或多个办法来获取与调集兼容的迭代器。 请注意, 回来办法的类型有必要被声明为迭代器接口, 因而详细调集能够回来各种不同品种的迭代器。

  4. 详细调集 (Concrete Collections) 会在客户端恳求迭代器时回来一个特定的详细迭代器类实体。 你可能会琢磨, 剩下的调集代码在什么地方呢? 不必忧虑, 它也会在同一个类中。 仅仅这些细节关于实际形式来说并不重要, 所以咱们将其省掉了而已。

  5. 客户端 (Client) 经过调集和迭代器的接口与两者进行交互。 这样一来客户端无需与详细类进行耦合, 答应同一客户端代码运用各种不同的调集和迭代器。

    客户端一般不会自行创立迭代器, 而是会从调集中获取。 但在特定情况下, 客户端能够直接创立一个迭代器 (例如当客户端需求自定义特别迭代器时)。

伪代码示例

在本例中, 迭代器形式用于遍历一个封装了拜访微信老友联系功用的特别调集。 该调集供给运用不同办法遍历档案资料的多个迭代器。

2023 跟我一起学习设计模式:  迭代器模式

遍历交际档案的示例

“老友 (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, "十分重要的音讯")

迭代器形式适合运用场景

当调集背后为杂乱的数据结构, 且你期望对客户端隐藏其杂乱性时 (出于运用便利性或安全性的考虑), 能够运用迭代器形式。

迭代器封装了与杂乱数据结构进行交互的细节, 为客户端供给多个拜访调集元素的简单办法。 这种办法不仅对客户端来说十分方便, 而且能避免客户端在直接与调集交互时执行错误或有害的操作, 然后起到保护调集的作用。

运用该形式能够削减程序中重复的遍历代码。

重要迭代算法的代码往往体积十分巨大。 当这些代码被放置在程序事务逻辑中时, 它会让原始代码的责任含糊不清, 下降其可维护性。 因而, 将遍历代码移到特定的迭代器中可使程序代码愈加精粹和简洁。

假如你期望代码能够遍历不同的乃至是无法预知的数据结构, 能够运用迭代器形式。

该形式为调集和迭代器供给了一些通用接口。 假如你在代码中运用了这些接口, 那么将其他完成了这些接口的调集和迭代器传递给它时, 它仍将能够正常运行。

完成办法

  1. 声明迭代器接口。 该接口有必要供给至少一个办法来获取调集中的下个元素。 但为了运用方便, 你还能够添加一些其他办法, 例如获取前一个元素、 记载当时方位和判断迭代是否已完毕。
  2. 声明调集接口并描绘一个获取迭代器的办法。 其回来值有必要是迭代器接口。 假如你计划拥有多组不同的迭代器, 则能够声明多个类似的办法。
  3. 为期望运用迭代器进行遍历的调集完成详细迭代器类。 迭代器方针有必要与单个调集实体链接。 链接联系一般经过迭代器的结构函数建立。
  4. 在你的调集类中完成调集接口。 其首要思维是针对特定调集为客户端代码供给创立迭代器的快捷办法。 调集方针有必要将本身传递给迭代器的结构函数来创立两者之间的链接。
  5. 查看客户端代码, 运用迭代器代替一切调集遍历代码。 每逢客户端需求遍历调集元素时都会获取一个新的迭代器。

迭代器形式优缺点

  • 单一责任原则。 经过将体积巨大的遍历算法代码抽取为独立的类, 你可对客户端代码和调集进行整理。

  • 开闭原则。 你可完成新型的调集和迭代器并将其传递给现有代码, 无需修正现有代码。

  • 你能够并行遍历同一调集, 因为每个迭代器方针都包含其本身的遍历状态。

  • 类似的, 你能够暂停遍历并在需求时继续。

  • 假如你的程序只与简单的调集进行交互, 运用该形式可能会矫枉过正。

  • 关于某些特别调集, 运用迭代器可能比直接遍历的功率低。

代码示例

  • Go 迭代器形式解说和代码示例 – 掘金 ()