Iterator
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”Iterator ให้วิธีเข้าถึงสมาชิกของคอลเล็กชันทีละตัวตามลำดับ โดยไม่เปิดเผยการแทนค่าภายในของคอลเล็กชัน ตรรกะการท่องผ่าน คือเราอยู่ที่ไหนและจะก้าวไปอย่างไร อยู่ใน object iterator ที่แยกต่างหาก ดังนั้นคอลเล็กชันเดียวกันจึงถูกผู้เรียกต่าง ๆ เดินผ่านได้อย่างเป็นอิสระ
คอลเล็กชันจำเป็นต้องถูกวนซ้ำ แต่ทุกคอลเล็กชันเก็บข้อมูลของตัวเองต่างกัน ได้แก่ อาเรย์ linked list ต้นไม้ ring buffer หากผู้เรียกเอื้อมเข้าไปข้างในเพื่อเดินผ่านข้อมูล พวกเขาก็เชื่อมติดกับที่จัดเก็บนั้น และการเปลี่ยนโครงสร้างก็ทำให้ทุกลูปพัง ที่แย่กว่านั้นคือผู้เรียกสองรายที่วนซ้ำพร้อมกันจะสะดุดกันเองหากตำแหน่งถูกเก็บไว้บนตัวคอลเล็กชันเอง
ความผันแปรที่ Iterator แยกออกมาคือ การท่องผ่าน pattern นี้ย้ายเคอร์เซอร์ คือตำแหน่งปัจจุบันและกฎสำหรับการก้าวต่อไป เข้าไปใน object ของตัวเอง ผู้เรียกขอ iterator จากคอลเล็กชันแล้วดึงสมาชิกผ่าน interface ที่เป็นแบบเดียวกัน โดยไม่ใส่ใจการจัดวางที่อยู่เบื้องหลัง เพราะแต่ละ iterator เป็นเจ้าของตำแหน่งของตัวเอง หลายตัวจึงท่องคอลเล็กชันเดียวกันได้พร้อมกัน
ปัจจุบันภาษาส่วนใหญ่มี iteration protocol ในตัว คุณจึงแทบไม่ต้องประดิษฐ์ interface คลาสสิกด้วยมือ คุณ implement protocol ของภาษาแทนแล้วได้ for-loop, comprehension และ lazy pipeline มาฟรี ๆ
โครงสร้าง
หัวข้อที่มีชื่อว่า “โครงสร้าง”classDiagram
class Iterable {
<<interface>>
+iterator() Iterator
}
class Iterator {
<<interface>>
+hasNext() bool
+next() T
}
class NumberRing {
-items: List~T~
+iterator() Iterator
}
class RingIterator {
-index: int
+hasNext() bool
+next() T
}
Iterable <|.. NumberRing
Iterator <|.. RingIterator
NumberRing ..> RingIterator : creates - Iterator — interface สำหรับการก้าวผ่านสมาชิก โดยทั่วไปคือ “มีอีกตัวไหม?” และ “ขอตัวถัดไป”
- Concrete Iterator — ถือตำแหน่งปัจจุบันและรู้วิธีก้าวต่อไปบนคอลเล็กชันหนึ่งโดยเฉพาะ
- Iterable (Aggregate) — คอลเล็กชัน สร้าง iterator เมื่อมีคำขอและซ่อนที่จัดเก็บของตัวเอง
- Client — ขอ iterator และบริโภคสมาชิกผ่าน interface โดยไม่รับรู้การจัดวาง
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”คอลเล็กชันแบบกำหนดเองขนาดเล็กที่ส่งสมาชิกออกมาตามลำดับ แต่ละภาษา implement iteration protocol ดั้งเดิม ของตัวเอง ได้แก่ Symbol.iterator และ generator ใน TypeScript, __iter__ และ generator ใน Python, generator แบบ closure ใน Go และ trait Iterator ใน Rust
class NumberBox implements Iterable<number> { private items: number[] = []; add(n: number): void { this.items.push(n); } // A generator is the idiomatic way to implement Symbol.iterator. *[Symbol.iterator](): Iterator<number> { for (const n of this.items) { yield n; } }}
const box = new NumberBox();box.add(1);box.add(2);box.add(3);for (const n of box) { console.log(n); // 1, 2, 3}console.log([...box]); // [1, 2, 3] — spread uses the same protocolfrom collections.abc import Iterator
class NumberBox: def __init__(self) -> None: self._items: list[int] = []
def add(self, n: int) -> None: self._items.append(n)
# A generator function makes __iter__ trivial and lazy. def __iter__(self) -> Iterator[int]: for n in self._items: yield n
box = NumberBox()box.add(1)box.add(2)box.add(3)for n in box: print(n) # 1, 2, 3print(list(box)) # [1, 2, 3]package main
import "fmt"
type NumberBox struct{ items []int }
func (b *NumberBox) Add(n int) { b.items = append(b.items, n) }
// Iterator returns a closure that yields the next element and a flag.// (Go 1.23+ range-over-func is another idiomatic option.)func (b *NumberBox) Iterator() func() (int, bool) { i := 0 return func() (int, bool) { if i >= len(b.items) { return 0, false } v := b.items[i] i++ return v, true }}
func main() { box := &NumberBox{} box.Add(1) box.Add(2) box.Add(3) next := box.Iterator() for v, ok := next(); ok; v, ok = next() { fmt.Println(v) // 1, 2, 3 }}struct NumberBox { items: Vec<i32>,}impl NumberBox { fn new() -> Self { NumberBox { items: Vec::new() } } fn add(&mut self, n: i32) { self.items.push(n); } // Borrow the items and lean on the standard Iterator trait. fn iter(&self) -> impl Iterator<Item = &i32> { self.items.iter() }}
fn main() { let mut box_ = NumberBox::new(); box_.add(1); box_.add(2); box_.add(3); for n in box_.iter() { println!("{}", n); // 1, 2, 3 } let collected: Vec<i32> = box_.iter().copied().collect(); println!("{:?}", collected); // [1, 2, 3]}ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”- ข้อดี: ผู้เรียกท่องคอลเล็กชันได้โดยไม่ต้องรู้หรือพึ่งพาที่จัดเก็บภายในของตัวเอง
- ข้อดี: แต่ละ iterator เป็นเจ้าของตำแหน่งของตัวเอง การท่องผ่านหลายครั้งของคอลเล็กชันเดียวจึงอยู่ร่วมกันได้
- ข้อดี: รวมการวนซ้ำข้ามโครงสร้างที่ต่างกันมหาศาลไว้หลัง interface เดียว และเข้าคู่กันอย่างเป็นธรรมชาติกับการประเมินผลแบบ lazy ตามต้องการ
- ข้อเสีย: สำหรับอาเรย์ธรรมดา ลูปดัชนีตรง ๆ ง่ายกว่า object iterator เฉพาะกิจ
- ข้อเสีย: การเปลี่ยนแปลงคอลเล็กชันขณะวนซ้ำเป็นแหล่งบั๊กคลาสสิก และ iterator ส่วนใหญ่ไม่ทนต่อสิ่งนี้
pattern ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “pattern ที่เกี่ยวข้อง”- ต้นไม้แบบ Composite เป็นบ้านตามธรรมชาติของ iterator ซึ่งสามารถแผ่โครงสร้างแบบเรียกซ้ำให้กลายเป็นการเดินเชิงเส้นได้
- Template Method มักขับเคลื่อนการท่องผ่าน composite โดย iterator เติมขั้นตอนต่อโหนดเข้าไป
| Iterator | Composite | Visitor | |
|---|---|---|---|
| จุดประสงค์ | วนซ้ำ collection ด้วย interface เดียว | tree ที่ treat leaf = branch | operation ใหม่บน structure เดิม |
| เข้าถึง | ทีละตัวตามลำดับ | recursive บน tree | traverse พร้อม operation |
| เปลี่ยน collection | ไม่ควร (อาจ corrupt) | ได้ | ไม่เปลี่ยน structure |
| ตัวอย่าง | for..of, generator, __iter__ | file system, DOM | AST visitor, report generator |
💡 หมายเหตุสำหรับ developer
Pattern นี้พบได้บ่อยใน:
- JavaScript
Symbol.iterator/for...of— ทำให้ object แบบไหนก็ตาม iterate ได้ด้วย syntax เดียวกัน- Python generator (
yield) — สร้าง iterator แบบ lazy โดยไม่ต้องโหลดข้อมูลทั้งหมดเข้า memory- Database cursor — iterate ผลลัพธ์ query ทีละแถวโดยไม่ต้องโหลดทั้ง result set มาไว้ใน memory