8

精读《设计模式 - Iterator 迭代器模式》

 3 years ago
source link: https://zhuanlan.zhihu.com/p/346788804
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

精读《设计模式 - Iterator 迭代器模式》

前端开发话题下的优秀答主

Iterator(迭代器模式)

Iterator(迭代器模式)属于行为型模式。

意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

这种设计模式要解决的根本问题是,聚合的种类有很多,比如对象、链表、数组、甚至自定义结构,但遍历这些结构时,不同结构的遍历方式又不同,所以我们必须了解每种结构的内部定义才能遍历。

比如数组我们可以利用 length + for 循环,对象我们可以 Object.keys,链表比较麻烦,需要内部暴露出元素的 next 以操作指向下一个元素。

迭代器模式可以做到用同一种 API 遍历任意类型聚合对象,且不用关心聚合对象的内部结构。

这种模式和 Array.from 有点像,但其实真正的迭代器在 JS 里是 obj[Symbol.iterator](),也就是一个对象实现了 [Symbol.interator],就认为是可遍历的。

举例子

如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。

迭代器的例子非常简单,我们平时工作中有大量使用到。

generator

generator 天生为迭代器的 API:

function* func () {
  yield 'a';
  yield 'b';
  return 'c';
}

var run = func();
run.next() // {value: "a", done: false}
run.next() // {value: "b", done: false}
run.next() // {value: "c", done: true}

我们无需关心 generator 内部是何种存储结构,只需要调用 .next(),并根据返回的 done 来判断是否遍历完即可。在 generator 的场景中,迭代器不仅用来遍历聚合,还用于执行代码。

数组迭代器

我们可以用迭代器的方式遍历数组:

const arr = [1, 2, 3]
const run = arr[Symbol.iterator]()

run.next() // {value: 1, done: false}
run.next() // {value: 2, done: false}
run.next() // {value: 2, done: false}
run.next() // {value: undefined, done: true}

可能有人觉得这是画蛇添足,因为毕竟遍历数组用 for 循环更方便,但这就是设计模式与非设计模式思维的区别,重要的不是用熟悉简单的 API 快速满足需求,设计模式关注的是如何统一、抽象、低耦合的编码

Map 迭代器

Map 对象也可以用迭代器方式遍历:

const citys = new Map([['北京', 1], ['上海', 2], ['杭州', 3]])
const run = citys.entries()

run.next() // {value: ['北京', 1], done: false}
run.next() // {value: ['上海', 2], done: false}
run.next() // {value: ['杭州', 3], done: false}
run.next() // {value: undefined, done: true}

意图解释

从上面的例子可以看出,虽然用迭代器遍历数组看上去比 for 循环麻烦一点,但当我们把所有聚合类型放到一起看时,可以发现只有迭代器的 API 是最统一的,是唯一一个不需要关心聚合类型就可以完成遍历的方案。

意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

再来看意图,就非常好理解了,我们无需关心 数组、generator、Map 内部是如何存储的,就可以进行遍历。实际上,深究 generator 内部的存储结构也没有意义,如果我们不用迭代器进行遍历,那么对于复杂结构的遍历成本是非常高的。

结构图

  • Aggregate: 聚合,需要定义创建迭代器的接口。比如前端规范的 [Symbol.iterator](),或者这里定义的 CreateIterator()
  • Iterator: 迭代器,定义了访问与遍历的 API。

迭代器的定义很简单,实现时要考虑的因素可不少,包括:

  • 健壮性。即迭代过程中增加、删除元素后,还能正常遍历。或者遍历空聚合时也要能正常工作。
  • 外部控制迭代还是内部。即类似 KOA 由插件调用 next() 控制迭代,还是由外层统一控制迭代。
  • 如何定义遍历算法。即便对于对象这种简单场景,也存在深度优先和广度优先、冒泡与捕获这几种遍历顺序,迭代器可以提供选择或者拓展的方式,自定义遍历算法。

代码例子

下面例子使用 typescript 编写。

// 定义聚合接口
interface Aggregate{
  getIterator: () => Iterator
}

// 定义迭代器接口
interface Iterator {
  // 指向下一个
  next: () => void
}

// 定义一个聚合
class List implements Aggregate {
  // 存储元素
  public values: string[]

  // 游标
  public index: number

  getIterator() {
    return new ConcreteIterator(this);
  }
}

// List 的迭代器
class ConcreteIterator implements Iterator {
  constructor(list: List) {
    this.list = list
  }

  next() {
    return this.list.values[this.list.index] // 注意边界情况,这里就不展开
    this.list.index++
  }
}

弊端

如果你只是遍历数组,直接用 for 循环会比迭代器方便很多,没必要为了用设计模式而用设计模式。迭代器仅在以下情况可以考虑用于数组:

  1. 这个数组比较特殊,是 N 维数组,需要一次性遍历完,那么可以用迭代器。
  2. 同时遍历数组和其他类型的聚合,则不论数组还是其他聚合,都用相同的迭代器模式遍历最好。

总结

迭代器模式比较好理解,这里补充几个相关设计模式:

  • 迭代器可以和组合模式配合,在组合结构内进行递归,这样一个迭代器就可以遍历完所有组合。
  • 可以用工厂模式 + 多态模式,实例化不同的迭代器的实例。
  • 迭代器模式还可以与备忘录模式配合使用,当我们要还原迭代器状态时,适合在迭代器内部使用备忘录模式进行状态存储。

讨论地址是:精读《设计模式 - Iterator 迭代器模式》· Issue #298 · dt-fe/weekly

如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证


Recommend

  • 10

    精读《设计模式 - Prototype 原型模式》前端开发话题下的优秀回答者Prototype(原型模式)Prototype(原型模式)属于创建型模式,既不是工厂也不是直接 New,而是...

  • 13

    精读《设计模式 - Composite 组合模式》前端开发话题下的优秀回答者Composite(组合模式)Composite(组合模式)属于结构型模式,是一种统一管理树形结构的抽象方...

  • 5
    • zhuanlan.zhihu.com 3 years ago
    • Cache

    精读《设计模式 - Bridge 桥接模式》

    精读《设计模式 - Bridge 桥接模式》前端开发话题下的优秀回答者Bridge(桥接模式)Bridge(桥接模式)属于结构型模式,是一种解决继承后灵活拓展的方案。...

  • 9

    精读《设计模式 - Adapter 适配器模式》前端开发话题下的优秀回答者Adapter(适配器模式)Adapter(适配器模式)属于结构型模式,别名 wrapper,结...

  • 14

    精读《设计模式 - Singleton 单例模式》前端开发话题下的优秀回答者Singleton(单例模式)Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一...

  • 12

    精读《设计模式 - Chain of Responsibility 职责链模式》前端开发话题下的优秀回答者Chain of Responsibility(职责链模式)Chain of Responsibility(职责链模式...

  • 11

    精读《设计模式 - Observer 观察者模式》前端开发话题下的优秀答主Observer(观察者模式)Observer(观察者模式)属于行为型模式。意图:定义对象间的...

  • 11

    精读《设计模式 - Strategy 策略模式》前端开发话题下的优秀答主Strategy(策略模式)Strategy(策略模式)属于行为型模式。意图:定义一系列的算法,...

  • 1

    精读《设计模式 - Visitor 访问者模式》前端开发话题下的优秀答主Visitor(访问者模式)Visitor(访问者模式)属于行为型模式。意图:表示一个作用于某...

  • 4
    • segmentfault.com 2 years ago
    • Cache

    设计模式--迭代器(Iterator)模式

    提供一中方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示迭代抽象:访问一个聚合对象的内部而无需暴露它的内部表示迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK