ข้ามไปยังเนื้อหา

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 ที่ถือเคอร์เซอร์และก้าวต่อไปอย่างเป็นอิสระ
  • 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 protocol
  • ข้อดี: ผู้เรียกท่องคอลเล็กชันได้โดยไม่ต้องรู้หรือพึ่งพาที่จัดเก็บภายในของตัวเอง
  • ข้อดี: แต่ละ iterator เป็นเจ้าของตำแหน่งของตัวเอง การท่องผ่านหลายครั้งของคอลเล็กชันเดียวจึงอยู่ร่วมกันได้
  • ข้อดี: รวมการวนซ้ำข้ามโครงสร้างที่ต่างกันมหาศาลไว้หลัง interface เดียว และเข้าคู่กันอย่างเป็นธรรมชาติกับการประเมินผลแบบ lazy ตามต้องการ
  • ข้อเสีย: สำหรับอาเรย์ธรรมดา ลูปดัชนีตรง ๆ ง่ายกว่า object iterator เฉพาะกิจ
  • ข้อเสีย: การเปลี่ยนแปลงคอลเล็กชันขณะวนซ้ำเป็นแหล่งบั๊กคลาสสิก และ iterator ส่วนใหญ่ไม่ทนต่อสิ่งนี้
  • ต้นไม้แบบ Composite เป็นบ้านตามธรรมชาติของ iterator ซึ่งสามารถแผ่โครงสร้างแบบเรียกซ้ำให้กลายเป็นการเดินเชิงเส้นได้
  • Template Method มักขับเคลื่อนการท่องผ่าน composite โดย iterator เติมขั้นตอนต่อโหนดเข้าไป
IteratorCompositeVisitor
จุดประสงค์วนซ้ำ collection ด้วย interface เดียวtree ที่ treat leaf = branchoperation ใหม่บน structure เดิม
เข้าถึงทีละตัวตามลำดับrecursive บน treetraverse พร้อม operation
เปลี่ยน collectionไม่ควร (อาจ corrupt)ได้ไม่เปลี่ยน structure
ตัวอย่างfor..of, generator, __iter__file system, DOMAST 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
Iterator pattern ให้อะไร?
ทำไม iterator หลายตัวจึงท่องคอลเล็กชันเดียวพร้อมกันได้?
วิธีที่เป็นสำนวนในการ implement การวนซ้ำในภาษาสมัยใหม่คืออะไร?
บั๊กที่พบบ่อยเมื่อใช้ iterator คืออะไร?