如何使用 TypeScript 的 IterableIterator<T> 實踐 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
TypeScript 提供了 IterableIterator<T>
與 IteratorResult<T>
2 個 interface,class 只要 implement IterableIterator<T>
interface,並由 next()
回傳 IteratorResult<T>
interface,就可以實做出 iterator,提供 for of
功能。
Architecture
Schoool
implement 了 IterableIterator<T>
interface,且 next()
回傳了 IteratorResult<T>
interface。
Implementation
Iterator
1 | interface Iterator<T> { |
定義 Iterator 該有的 method
一個 iterator 一定要有 next()
,而 return()
與 throw()
可視需求決定是否 implement。
IterableIterator
1 | interface IterableIterator<T> extends Iterator<T> { |
定義 Iterator 該有的 property
由於 IterableIterable<T>
繼承了 Iterator<T>
,也就是 next()
、return()
與 throw()
3 個 method 都會存在於 IterableIterator<T>
,另外還多了自己的 Symbol.iterator
property。
簡單的說,一個 class 要成為 iterator,必須 implement IterableIterator<T>
:
Symbol.iterator
property : 回傳的型別為IterableIterator<T>
next()
、return()
與throw()
: 3 個 method
才可稱為 iterator 被 for of
所使用。
Iterator<T>
與IterableIterator<T>
可視為因為界面隔離原則
,所以拆成 2 個 interface,但因為最後由IterableIterator<T>
繼承了Iterator<T>
,所以對於使用端而言,只要知道IterableIterator<T>
即可,符合最小知識原則
。
IteratorResult
1 | interface IteratorResult<T> { |
定義 next() 的回傳結果
next()
回傳的 object,一定要有 done
與 value
兩個 property。
done
:false
不是最後一筆資料,true
為最後一筆資料value
: 要傳回的 itertator 資料
Student.ts
1 | export interface Student { |
定義資料類型為 Student
interface,只有 Name
public field。
School.ts
1 | import { Student } from './student'; |
第 3 行
1 | export class School<T extends Student> implements IterableIterator<T> { |
為了 School
實踐 iterator,使其 implement IterableIterator<T>
,但這個 T
並不是任意的 T
,被限定只能繼承於 Student
interface。
第 4 行
1 | private students: T[]; |
School
的內部資料 Student[]
,其中 Student
以 T
取代。
第 5 行
1 | private index = 0; |
儲存目前 students
的 index 位置。
第 7 行
1 | constructor() { |
在 constructor 將目前的 students
資料建立起來。
15 行
1 | [Symbol.iterator](): IterableIterator<T> { |
Symbol.iterator
property 回傳一個IterableIterator<T>
物件供使用端操作
根據 IterableIterator<T>
規定,一定要提供 Symbol.iterator
property,且回傳型別 IterableIterator<T>
,而 School
正是 implement IterableIterator<T>
,因此可直接回傳。
19 行
1 | next(): IteratorResult<T> { |
next()
method 回傳IteratorResult<T>
物件供使用端取值
根據 Iterator<T>
規定,一定要提供 next()
,且回傳值型態為 IteratorResult<T>
。
若目前的 index 還在範圍內,則 done
為 false
,value
為 students
目前 index 的資料。
若 index 已經到最後一筆,則 done
為 true
,value
為 undefined
。
FAQ
Q : 為什麼要使用
for of
就要 implementIterableIterator<T>
interface ?
1 | const school = new School<Student>(); |
經過編譯後會變成
1 | const school = new School<Student>(); |
這也是為什麼我們必須 implement IterableIterator<T>
interface 的原因,因為真的會用到 IterableIterator<T>
的 next()
與 IteratorResult<T>
的 done
與 value
。
Conclusion
- Iterator Pattern 讓我們不用暴露 object 內部資料,卻又可以讓使用端以
for of
方式訪問內部資料。 - Iterator Pattern 在實務上可以讓我們避免回傳一個大陣列影響效能,大陣列需要浪費時間建立,也浪費記憶體。