本文源代碼地址: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()
方法。
關於此模式的具體實現,咱們能夠先來看一個例子 —— 遍歷人名。
咱們一步一步按照類圖來進行實現。
首先定義 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
了。它包含兩個方法,next
和 hasNext
。
另外,該類還包含兩個私有屬性,分別是咱們的 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
類型,咱們都無需再次更新咱們的迭代器實現,只管直接拿來用就好了。
相對於面向對象中的 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 模式爲本系列的第一個模式,後續模式中將會講解此模式與其餘模式的對比。