Iterator Pattern(迭代器模式)
是什麼?
Iterator Pattern 提供一種方法循序存取集合中的每個元素,而不需要暴露集合的底層資料結構。它把「如何遍歷」的邏輯從集合中抽離出來,封裝在獨立的迭代器物件中。
ℹ️GoF 分類
Iterator 屬於行為型模式(Behavioral Pattern),重點在於提供統一的方式來走訪不同類型的集合。
什麼時候用?
- 你想統一不同資料結構(陣列、樹、圖)的走訪方式
- 你需要同時支援多種走訪策略(正向、反向、篩選)
- 你想隱藏集合的內部結構,只暴露元素
- 你需要支援延遲載入(Lazy Evaluation)——一次只取一個元素
什麼時候不該用?
⚠️過度設計警告
現代語言(C#、Python、Java、TypeScript)都內建了 Iterator 機制。除非你在開發自訂集合或需要特殊的走訪邏輯,否則直接用語言內建的 foreach、for...of 就好,不需要手動實作。
執行流程
定義 Iterator 介面
宣告 HasNext、Next 等走訪方法
定義 IterableCollection 介面
宣告 CreateIterator 方法
實作具體集合
儲存元素並建立對應的迭代器
實作具體 Iterator
追蹤目前位置,實作走訪邏輯
Client 使用迭代器
透過 HasNext/Next 統一走訪,不關心底層結構
流程解讀:Iterator 把「走訪邏輯」從集合中抽離。集合只負責儲存元素和建立 Iterator,Iterator 負責追蹤走訪位置和決定走訪順序。同一個集合可以提供多種 Iterator(正向、反向、篩選),Client 透過統一的 HasNext/Next 介面走訪,完全不需要知道底層是陣列、鏈結串列還是資料庫。
程式碼範例
C# 版本
using System.Collections;
using System.Collections.Generic;
// 1. 自訂集合:播放清單
public class Playlist : IEnumerable<string>
{
private readonly List<string> _songs = new();
public void AddSong(string song) => _songs.Add(song);
public int Count => _songs.Count;
// 正向迭代器
public IEnumerator<string> GetEnumerator()
{
for (int i = 0; i < _songs.Count; i++)
yield return _songs[i];
}
// 反向迭代器
public IEnumerable<string> Reverse()
{
for (int i = _songs.Count - 1; i >= 0; i--)
yield return _songs[i];
}
// 篩選迭代器
public IEnumerable<string> FilterBy(string keyword)
{
foreach (var song in _songs)
if (song.Contains(keyword))
yield return song;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
// 2. 使用
var playlist = new Playlist();
playlist.AddSong("Bohemian Rhapsody");
playlist.AddSong("Hotel California");
playlist.AddSong("Bohemian Grove");
Console.WriteLine("正向播放:");
foreach (var song in playlist)
Console.WriteLine($" ♪ {song}");
Console.WriteLine("反向播放:");
foreach (var song in playlist.Reverse())
Console.WriteLine($" ♪ {song}");
Console.WriteLine("搜尋 Bohemian:");
foreach (var song in playlist.FilterBy("Bohemian"))
Console.WriteLine($" ♪ {song}");TypeScript 版本
// 1. 自訂集合:播放清單
class Playlist implements Iterable<string> {
private songs: string[] = [];
addSong(song: string) { this.songs.push(song); }
// 正向迭代器
[Symbol.iterator](): Iterator<string> {
let index = 0;
const songs = this.songs;
return {
next(): IteratorResult<string> {
if (index < songs.length) {
return { value: songs[index++], done: false };
}
return { value: undefined as any, done: true };
}
};
}
// 反向迭代器
*reverse(): IterableIterator<string> {
for (let i = this.songs.length - 1; i >= 0; i--) {
yield this.songs[i];
}
}
// 篩選迭代器
*filterBy(keyword: string): IterableIterator<string> {
for (const song of this.songs) {
if (song.includes(keyword)) yield song;
}
}
}
// 2. 使用
const playlist = new Playlist();
playlist.addSong("Bohemian Rhapsody");
playlist.addSong("Hotel California");
playlist.addSong("Bohemian Grove");
console.log("正向播放:");
for (const song of playlist) console.log(` ♪ ${song}`);
console.log("反向播放:");
for (const song of playlist.reverse()) console.log(` ♪ ${song}`);
console.log("搜尋 Bohemian:");
for (const song of playlist.filterBy("Bohemian")) console.log(` ♪ ${song}`);Python 版本
from typing import Iterator
# 1. 自訂集合:播放清單
class Playlist:
def __init__(self):
self._songs: list[str] = []
def add_song(self, song: str) -> None:
self._songs.append(song)
# 正向迭代器
def __iter__(self) -> Iterator[str]:
return iter(self._songs)
# 反向迭代器
def reverse(self) -> Iterator[str]:
for i in range(len(self._songs) - 1, -1, -1):
yield self._songs[i]
# 篩選迭代器
def filter_by(self, keyword: str) -> Iterator[str]:
for song in self._songs:
if keyword in song:
yield song
# 2. 使用
playlist = Playlist()
playlist.add_song("Bohemian Rhapsody")
playlist.add_song("Hotel California")
playlist.add_song("Bohemian Grove")
print("正向播放:")
for song in playlist:
print(f" ♪ {song}")
print("反向播放:")
for song in playlist.reverse():
print(f" ♪ {song}")
print("搜尋 Bohemian:")
for song in playlist.filter_by("Bohemian"):
print(f" ♪ {song}")Java 版本
import java.util.*;
// 1. 自訂集合:播放清單
public class Playlist implements Iterable<String> {
private final List<String> songs = new ArrayList<>();
public void addSong(String song) { songs.add(song); }
// 正向迭代器
@Override
public Iterator<String> iterator() {
return songs.iterator();
}
// 反向迭代器
public Iterable<String> reverse() {
return () -> new Iterator<String>() {
private int index = songs.size() - 1;
public boolean hasNext() { return index >= 0; }
public String next() { return songs.get(index--); }
};
}
// 篩選迭代器
public Iterable<String> filterBy(String keyword) {
return () -> songs.stream()
.filter(s -> s.contains(keyword))
.iterator();
}
}
// 2. 使用
Playlist playlist = new Playlist();
playlist.addSong("Bohemian Rhapsody");
playlist.addSong("Hotel California");
playlist.addSong("Bohemian Grove");
System.out.println("正向播放:");
for (String song : playlist)
System.out.println(" ♪ " + song);
System.out.println("反向播放:");
for (String song : playlist.reverse())
System.out.println(" ♪ " + song);
System.out.println("搜尋 Bohemian:");
for (String song : playlist.filterBy("Bohemian"))
System.out.println(" ♪ " + song);結構圖
結構解讀:Client 向 IterableCollection 請求一個 Iterator,Collection 建立並回傳對應的 ConcreteIterator。Client 之後只透過 Iterator Interface(HasNext/Next)走訪,完全不知道底層是陣列、連結串列還是資料庫。同一個 Collection 可以同時建立多個獨立的 Iterator,各自追蹤自己的走訪位置。
實戰補充
💡資深開發者經驗
C# 的 yield return:C# 的 yield return 是 Iterator Pattern 的語法糖,編譯器會自動產生狀態機。搭配 LINQ 的延遲執行(Lazy Evaluation),可以處理無限序列而不會耗盡記憶體。
Python 的 Generator:Python 的 yield 和 Generator Expression 讓 Iterator 的實作極為簡潔。itertools 模組提供了大量實用的 Iterator 工具。
自訂集合的必要性:當你寫了 class TreeNode 這類自訂資料結構時,實作 Iterator 讓使用者可以用 foreach 走訪是必要的設計。可以提供 DFS、BFS 等不同的走訪策略。
效能考量:Iterator 的延遲載入特性讓你可以處理超大資料集——一次只載入一筆資料到記憶體。這在處理檔案串流、資料庫查詢結果時特別有用。
理解測驗
🤔 Iterator Pattern 的核心目的是什麼?
🤔 C# 的 yield return 對應 Iterator Pattern 的哪個概念?
🤔 什麼情況下你需要手動實作 Iterator Pattern?
面試常見問題
Q: Internal Iterator 和 External Iterator 有什麼差別?
A: External Iterator 由 Client 控制走訪步調(手動呼叫 Next()),如 Java 的 Iterator。Internal Iterator 由集合自己控制走訪(傳入 callback),如 JavaScript 的 forEach()。External 更靈活(可以提前中斷、同時走訪兩個集合),Internal 更簡潔。
Q: Lazy Evaluation 在 Iterator 中如何應用?
A: Lazy Evaluation(惰性求值,指延遲到真正需要時才計算)讓 Iterator 可以處理無限序列或超大資料集。C# 的 yield return、Python 的 yield 都實現了 Lazy Evaluation — 每次 Next() 才計算下一個元素,不需要一次把所有元素載入記憶體。
相關模式
| 模式 | 關係 |
|------|------|
| Composite | Iterator 常用來走訪 Composite 的樹狀結構,提供 DFS 或 BFS 等走訪策略 |
| Visitor | 都用來操作集合中的元素,但 Iterator 專注「走訪順序」,Visitor 專注「對不同型別做不同操作」 |
| Factory Method | Collection 的 CreateIterator() 就是一個 Factory Method,由具體集合決定建立哪種 Iterator |
| Memento | Iterator 的走訪位置可以用 Memento 保存,實現「書籤」功能 |
重點整理
💡一句話記住
Iterator Pattern = 播放器按鈕:不管歌單存在哪,按「下一首」就對了。 口訣:「統一走訪,隱藏結構」
| 概念 | 說明 | |------|------| | Iterator(迭代器介面) | 定義 HasNext、Next 等走訪方法 | | ConcreteIterator | 追蹤走訪位置,實作具體走訪邏輯 | | IterableCollection | 提供建立 Iterator 的方法 | | 核心好處 | 統一走訪介面、支援多種走訪策略 | | 代價 | 簡單集合不需要,現代語言大多已內建支援 |