发布于 2026-01-06 0 阅读
0

理解设计模式:使用 Dev.to 和 Medium 社交网络的迭代器!

理解设计模式:使用 Dev.to 和 Medium 社交网络的迭代器!

《设计模式:可复用面向对象软件的基础》一书中描述了23种经典设计模式。这些模式为软件开发中经常重复出现的特定问题提供了解决方案。

在本文中,我将描述什么是迭代器模式;以及如何以及何时应用它。

迭代器模式:基本概念

在面向对象编程中,迭代器模式是一种设计模式,它使用迭代器遍历容器并访问容器中的元素。迭代器模式将算法与容器解耦;但在某些情况下,算法必然与容器相关,因此无法解耦。——维基百科

提供一种按顺序访问聚合对象元素的方法,而无需暴露其底层表示。——《设计模式:可复用面向对象软件的基础》

这种模式的主要特点是允许你在不暴露底层表示形式(数组、映射、树等)的情况下遍历集合中的元素。因此,这种模式解决了以下两个问题:

  1. 允许我们在不改变算法实现的情况下,改变集合的内部实现。

  2. 允许我们添加适用于所有现有集合类型的新算法。

总而言之,迭代器模式对客户端隐藏了集合的内部实现。该模式的 UML 图如下所示:

Iterator 类是一个接口,它定义了遍历集合的不同操作(next 或 hasNext),而 Aggregate 类则会创建 Iterator。最后,系统将使用 ConcreteAggregate 和 ConcreteIterator。

  1. 您的集合底层具有复杂的数据结构,但您希望对客户端隐藏其复杂性。

  2. 你需要减少应用程序中重复的遍历代码。

  3. 你希望你的代码能够遍历不同的数据结构。

迭代器模式具有以下几个优点:

  • 由于迭代器采用了单一职责开闭原则,因此代码更容易使用、理解和测试。

  • 单一职责原则使我们能够清理遍历算法的客户端和集合。

  • 开闭原则允许在不破坏任何现有功能的情况下实现新型集合和迭代器。

  • 对同一集合进行并行迭代,因为每个迭代器对象都包含自己的迭代状态。

  • 代码简洁,因为客户端/上下文未使用复杂的接口,系统更加灵活且可重用

接下来,我将向您展示如何使用 JavaScript/TypeScript 实现这种模式。在本例中,我创建了一个问题:有一个名为 WordsCollection 的类,它定义了一个单词列表(items)以及用于获取和添加单词的方法(getItems 和 addItem)。客户端使用诸如 for 或 forEach 之类的控制结构来调用这个类。下面的 UML 图展示了我刚才描述的场景。

WordsCollection 关联的代码如下:

客户端代码关联项如下:

这个方案的主要问题在于代码耦合性。也就是说,客户端需要了解集合的内部结构才能实现两个遍历方法(直线遍历和反向遍历)。试想一下,如果你需要将数据结构从数组更改为映射,那么由于这种耦合性,与客户端相关的代码就会出错。迭代器模式的另一个有趣用例是当你需要一种新的集合遍历方式时,例如按字母顺序遍历

解决方案是使用迭代器模式,使用此模式的新 UML 图如下所示:

因此,该解决方案包含一个接口类(迭代器),该类定义了遍历集合的方法:

  1. current(): T.

  2. key(): 数字。

  3. hasMoreElements(): 布尔值。

  4. 倒带:无效。

AlphabeticalOrderIterator 类是一个迭代器,负责实现以正确方式遍历集合的方法。该迭代器需要使用聚合方式的 WordsCollection 集合以及迭代方式(反向或正向)。因此,与 AlphabeticalOrderIterator 相关的代码如下:

下一步是定义聚合器接口,并修改集合以实现该接口。因此,与聚合器相关的代码如下:

请注意,Aggregator 接口定义了创建新迭代器的方法。在本题中,我们需要两个迭代器:Straight 和 Reverse。因此,WordsCollection 集合需要进行修改以包含这些方法,如下面的代码所示:

最后,我们可以在客户端代码中使用迭代器,现在客户端代码已经解耦,如下面的代码所示:

客户端与 WordsCollection 类的内部结构解耦(单一职责),并且您可以通过实现新的迭代器来扩展软件(开放/封闭)。

我创建了几个 npm 脚本,在应用迭代器模式后运行此处显示的代码示例。

npm run example1-problem
npm run example1-iterator-solution-1

假设我们需要开发一款软件,用于向社交网络中的联系人发送电子邮件,并且需要区分邮件类型。我们的联系人网络分为两类:朋友和同事。根据收件人的类型,邮件的正式程度也会有所不同。

首先,我们从两个知名的社交网络平台 Dev.to 和 Medium 上获取联系人信息(不用多说,大家都知道我最喜欢哪个!:-))。这两个平台的数据结构实现方式不同,Dev.to 使用数组来维护联系人信息,而 Medium 则使用 Map。

迭代器模式将使我们的代码与联系人和社交网络完全解耦,使我们能够从每个社交网络的内部实现中抽象出来,甚至能够添加新的社交网络(尽管……对于我们这些极客来说,真的还有其他社交网络存在吗? :P)。

下面有一个 gif 动画,展示了客户端如何使用我们的整个结构(我已经做了一个简单的 CLI 示例)。

在下面的 UML 图中,您可以看到针对此问题提出的解决方案:

好的,这个问题中的模型不是字符串,而是用户个人资料,如下面的代码所示:

在 Profile 类中,我们有一个 getContactsByType 方法,它会返回朋友或同事的联系人。

下一步是定义迭代器接口(ProfileIterator)和聚合器接口(SocialNetwork),它们定义了每个迭代器和聚合器必须实现的方法。

因此,与这些接口相关的代码如下:

现在,我们需要将前面提到的接口具体化,以解决我们的问题。我们将要解决的第一个社交网络是 Dev.to。聚合器和迭代器的实现如下所示。

请注意,存储联系人的集合是一个数组,并且已实现 `createFriendsIterator` 和 `createCoworkersIterator` 方法。它包含多个模拟连接到远程 API 以获取联系人的方法。

与 DevToIterator 类关联的代码如下:

前面代码中最重要的部分是接口实现。具体的实现基于集合的内部数据结构(数组)。您可能会注意到,我开发了一个惰性方法来请求联系人(请仔细考虑这一点。如果我请求某个朋友的所有朋友,可能会导致无限循环)。

好了,现在我们应该创建 SocialSpammer 类,该类仅使用接口。正如您在以下代码中看到的,SocialSpammer 类与任何具体类都是解耦的:

之前的代码根据邮件的收件人是朋友还是同事而使用不同的迭代器。

现在,我们可以在以下客户端中使用该代码:

现在是时候检验我们是否能够利用开放/封闭原则创建一个新的社交网络及其迭代器,而不会破坏我们的应用程序。

与中等水平相关的代码如下:

我们本可以使用继承来简化 Dev.to 和 Medium 之间的代码,但为了避免本文篇幅过长,我们选择重复编写代码。您可以看到,Medium 类使用了不同的数据结构来存储联系人。

最后,中等迭代器如下所示:

我创建了一个 npm 脚本,它在应用迭代器模式和 CLI 界面后运行此处显示的示例。

npm run example2-iterator-solution1

迭代器模式可以避免项目中的代码耦合。当集合中包含多个算法和数据结构时,迭代器模式非常适用。由于应用了单一职责开闭原则这两个著名的原则,你的代码会更加简洁。

最重要的不是照着我演示的模式去实现,而是要能够识别出这种特定模式可以解决的问题,以及何时应该或不应该使用它。这一点至关重要,因为具体的实现方式会因你使用的编程语言而异。

原文发表于https://www.carloscaballero.io,日期为 2019 年 6 月 12 日。

文章来源:https://dev.to/carlillo/understanding-design-patterns-iterator-using-dev-to-and-medium-social-networks-3bdd