谈谈设计模式 —— Iterator

本文源代码地址:github.com/Erichain/de…javascript

最近在阅读《图解设计模式》一书,书上每个设计模式涉及的篇幅不是太长,可是,知识点却都涵盖了进去。在学习的同时,打算加上本身的理解,将这二十三种设计模式分篇章的一一分享出来。同时,结合相关的范例,咱们将这些设计模式以 JavaScript 和 TypeScript 的方式来进行实现。java

本文是分享的第一个设计模式 —— Iterator(迭代器模式)。git

简介

只要你是一个程序员,那么,你确定接触过循环,不管是普通的 for 循环仍是 while 循环,或者是 for-of,for-in 循环等。从某种意义上来讲,这些循环都属于遍历的范畴。程序员

好比:github

for (let i: number = 0; i < list.length; i++) {
  console.log(list[i]);
}复制代码

这是一个使用 for 循环实现的最简单的迭代器,能够依次输出 list 中的元素。typescript

咱们能够注意到,咱们的 list 是一个数组,能够直接使用 for 循环来进行遍历,可是,要是咱们的 list 长度不必定呢?那么,咱们每次进行遍历的时候,就会存在更改迭代器实现的问题。这样的话,就违反了咱们所说的开闭原则,因此,咱们要引入 Iterator 模式。编程

概念

用一句话来讲,Iterator 模式就是分离集合和迭代器的职责。设计模式

让迭代器的实现不依赖于咱们的集合,不管集合怎么更改,都不用去更改迭代器,这就是 Iterator 模式的目的所在。数组

涉及的名词

Aggregate(Interface)函数

集合接口,包含一个 iterator() 方法,用于建立迭代器。

Concrete Aggregate

具体的集合类,用于实现集合接口的 iterator() 方法来建立迭代器,以及定义集合本身拥有的方法。

Iterator(Interface)

迭代器接口,用于遍历集合,包含 next()hasNext() 方法。

Concrete Iterator

具体的迭代器类,用于实现 Iterator 接口中的 next()hasNext() 方法。

类图

Iterator 类图
Iterator 类图

实现

Example 1

关于此模式的具体实现,咱们能够先来看一个例子 —— 遍历人名。

咱们一步一步按照类图来进行实现。

首先定义 Aggregate 接口,咱们将其命名为 NamesList,它包含有一个 iterator 方法,返回一个 iterator,而这个 iterator 类型将由咱们后面的 Iterator 接口定义,咱们暂且将其命名为 NamesIterator

interface NamesList {
  iterator(): NamesIterator;
}复制代码

而后,咱们须要定义咱们的 Iterator 接口 NamesIterator,包含了 next()hasNext() 方法。

next() 方法用来对咱们的 NamesList 进行遍历,每调用一次该方法,内部的指针(简单来讲就是内部用于标识当前元素的变量)就会指向下一个元素。

hasNext() 方法即用来表示是否还存在下一个元素。

interface NamesIterator {
  next(): string;
  hasNext(): boolean;
}复制代码

接口定义完成以后,咱们如今须要作的就是实现这两个接口。

那么,首先是咱们的 NamesList 接口。固然,咱们具体的 NamesList 确定不止是 iterator 这一个方法了。咱们还须要新增元素的方法 add,删除元素的方法 deleteNameByIndex,获取 list 长度的方法 getLength,获取指定元素的方法 getNameByIndex

class ConcreteNamesList implements NamesList {
  private namesList: Array<string> = [];

  add(name: string): void {
    this.namesList.push(name);
  }

  deleteNameByIndex(index: number): void {
    this.namesList.splice(index, 1);
  }

  getNameByIndex(index: number): string {
    return this.namesList[index];
  }

  getLength(): number {
    return this.namesList.length;
  }

  iterator(): NamesIterator {
    return new ConcreteNamesIterator(this);
  }
}复制代码

以上就是咱们所定义的 ConcreteNamesList 类了,即类图中的 ConcreteAggregate

那么,如今咱们须要的就是实现咱们的 ConcreteNamesIterator 了。它包含两个方法,nexthasNext

另外,该类还包含两个私有属性,分别是咱们的 ConcreteNamesList 实例和当前所迭代的索引值 currentIndex。每调用一次 next 方法,索引值自动加 1。

class ConcreteNamesIterator implements NamesIterator {
  private namesList: ConcreteNamesList;
  private currentIndex: number = 0;

  constructor(namesList: ConcreteNamesList) {
    this.namesList = namesList;
  }

  hasNext(): boolean {
    return this.currentIndex < this.namesList.getLength();
  }

  next(): string {
    const currentName: string = this.namesList.getNameByIndex(this.currentIndex);
    this.currentIndex++;

    return currentName;
  }
}复制代码

定义好了咱们全部的类和方法以后,咱们就能够直接使用 Iterator 来对咱们的 NamesList 进行遍历了。

const namesList: ConcreteNamesList = new ConcreteNamesList();

namesList.add('a');
namesList.add('b');
namesList.add('c');

const it: NamesIterator = namesList.iterator();

while (it.hasNext()) {
  it.next(); // a, b, c
}复制代码

咱们将集合的实现和 Iterator 的实现分开,这样,他们之间就不会存在互相影响的问题,不管咱们怎么修改咱们的 NamesList,不管是添加仍是删除元素,只要这个集合可以返回可用的 NamesIterator 类型,咱们都无需再次更新咱们的迭代器实现,只管直接拿来用就好了。

Example 2

相对于面向对象中的 Iterator 模式,咱们在 JavaScript 中也可以找到 Iterator 模式的影子 —— Symbol.iterator

在 JavaScript 中,可使用 Symbol.iterator 来定义一个对象的迭代方法,就像下面这样:

const myObj = {
  foo: 'foo',
  bar: 'bar',
  baz: 'baz',

  *[Symbol.iterator]() {
    for (let key in this) {
      yield [key, this[key]];
    }
  },
};复制代码

而后,咱们就能够直接对这个对象进行遍历了:

for (let item of myObj) {
  console.log(item);

  // ["foo", "foo"]
  // ["bar", "bar"]
  // ["baz", "baz"]
}复制代码

咱们能够注意到,包含 Symbol.iterator 的这个函数其实就是一个 Iterator,咱们若是将其抽象出来,那么,无论咱们的对象是什么样,咱们均可以在不更改这个函数的状况下对对象进行遍历。

固然,咱们也能够按照面向对象的方式来实现对对象的 Iterator 模式。因为 JavaScript 里没有接口和抽象类的概念,因此咱们此处就直接采用 class 来实现了。

本例中,咱们将书本信息存成一个对象,而后对其进行遍历。

一样的,咱们先实现咱们的集合类 BooksMap

class BooksMap {
  constructor() {
    this.booksMap = {};
  }

  addBook(key, value) {
    Object.assign(this.booksMap, {
      [key]: value,
    });
  }

  deleteBookByKey(key) {
    delete this.booksMap[key];
  }

  getBookByKey(key) {
    return this.booksMap[key];
  }

  getBookKeys() {
    return Object.keys(this.booksMap);
  }

  getBooksCount() {
    return Object.keys(this.booksMap).length;
  }

  iterator() {
    return new BooksIterator(this);
  }
}复制代码

而后是咱们的 BooksIterator 类。

class BooksIterator {
  constructor(booksMapInstance) {
    this.booksMapInstance = booksMapInstance;
    this.currentIndex = 0;
  }

  next() {
    const currentKey = this.booksMapInstance.getBookKeys()[this.currentIndex];
    const currentItem = this.booksMapInstance.getBookByKey(currentKey);

    this.currentIndex++;

    return currentItem;
  }

  hasNext() {
    return this.currentIndex < this.booksMapInstance.getBooksCount();
  }
}复制代码

咱们能够看到,其实这两个类所包含的方法和实现与咱们以前实现的 NamesList 相似,只是说,本例中遍历的集合类型换成了对象而已。具体集合的实现和 Iterator 的实现并不耦合,咱们修改了对象以后,依旧可使用定义好的 Iterator。

总结

在第一个例子和第二个例子的后半部分中,咱们采用了面向对象的方式来实现 Iterator 模式,这实际上是必要的。JavaScript 原本就是一门面向对象的语言,只是说,在某些方面,与 Java 这种高级语言相比还欠缺了许多东西,因此咱们第一个例子会采用 TypeScript 来代替 JavaScript 实现。

其实,使用抽象的类或者接口是为了让咱们更好的解耦各个类。一味的使用具体的类来进行编程的话会致使各个类之间的强耦合,也就会存在难以复用和维护的问题。

咱们将在后续的文章中延续这种方式来说解,以便于更容易理解。

对比其余模式

Iterator 模式为本系列的第一个模式,后续模式中将会讲解此模式与其余模式的对比。

相关文章
相关标签/搜索