談談設計模式 —— 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 模式爲本系列的第一個模式,後續模式中將會講解此模式與其餘模式的對比。

相關文章
相關標籤/搜索