如何使用 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.iteratorproperty : 回傳的型別為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.iteratorproperty 回傳一個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 在實務上可以讓我們避免回傳一個大陣列影響效能,大陣列需要浪費時間建立,也浪費記憶體。