本文源代码地址: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 模式为本系列的第一个模式,后续模式中将会讲解此模式与其余模式的对比。