如何使用 TypeScript 的 Generator 實踐 Iterator Pattern ?
在 C# 有著名的 IEnumerable
與 IEnumerator
interface,只要 implement 這兩個 interface,就可以實踐 Iterator Pattern,但 TypeScript 該如何實做呢 ?
Version
TypeScript 2.6
User Story
1 | const school = new School<Student>(); |
School
內包含很多 Student
,我們希望直接使用 for of
對 School
抓到所有 Student
。
for of
為 ES6 的新語法,可以想像成是 C# 的foreach()
。
Task
yield
本身回傳就是 iterator,因此若使用 yield
將更精簡。
Architecture
Schoool
implement 了 Iterable<T>
interface,且必須回傳 Iterator<T>
interface。
Implementation
Iterable1
2
3interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
定義 Iterator 該有的 method
一個 iterator 一定要有 [Symbol.iterator]()
,並且回傳 Iterator<T>
,其中 yield
可直接回傳 Iterator<T>
。
Iterator1
2
3
4
5interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
定義
[Symbol.iterator]()
回傳結果的 interface
不用擔心要實作這個 interface 很困難,yield
回傳的正是 Iterator<T>
,只要簡單使用 yield
即可。
Student.ts1
2
3export interface Student {
Name: string;
}
定義資料類型為 Student
interface,只有 Name
public field。
School.ts1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import { Student } from './student';
export class School<T extends Student> implements Iterable<T> {
private students: T[];
constructor() {
this.students = [
<T>{ Name: 'Sam' },
<T>{ Name: 'Kevin' },
<T>{ Name: 'Jessie' }
];
}
*[Symbol.iterator]() {
for (const iter of this.students)
{
yield iter;
}
}
}
第 3 行
1 | export class School<T extends Student> implements Iterable<T> { |
為了 School
實踐 iterator,使其 implement IterableIterator<T>
,但這個 T
並不是任意的 T
,被限定只能繼承於 Student
interface。
第 4 行
1 | private students: T[]; |
School
的內部資料 Student[]
,其中 Student
以 T
取代。
第 6 行
1 | constructor() { |
在 constructor 將目前的 students
資料建立起來。
14 行
1 | *[Symbol.iterator]() { |
Symbol.iterator
property 回傳一個Iterator<T>
物件供使用端操作
根據 Iterable<T>
規定,一定要提供 Symbol.iterator
property,且回傳型別 Iterator<T>
,而 yield
回傳的正是 Iterator<T>
。
yield
與 return
不同的是 : 執行到 yield
會立即中止,當一個 for...of
執行時,會繼續下一個 yield
,因此不像 return
是回傳一個陣列,然後使用端又不須再一次 for..of
。
使用
yield
前後只有一次for...of
。
另外 ES6 規定,只要回傳的是 iterator,則 function 之前要加 *
。
除此之外,ES6 提供更精簡的寫法
1 | *[Symbol.iterator]() { |
只要 yield*
,後面加上 array,編譯展開相當於
1 | for (const iter of this.students) |
Conclusion
- Iterator Pattern 讓我們不用暴露 object 內部資料,卻又可以讓使用端以
for of
方式訪問內部資料。 - Iterator Pattern 在實務上可以讓我們避免回傳一個大陣列影響效能,大陣列需要浪費時間建立,也浪費記憶體。
- 若使用 generator,則程式碼更短,意圖也更明顯,還可以使用更精簡的
yield*
寫法。