Iterator Pattern(迭代器模式)

是什麼?

Iterator Pattern 提供一種方法循序存取集合中的每個元素,而不需要暴露集合的底層資料結構。它把「如何遍歷」的邏輯從集合中抽離出來,封裝在獨立的迭代器物件中。

ℹ️GoF 分類

Iterator 屬於行為型模式(Behavioral Pattern),重點在於提供統一的方式來走訪不同類型的集合。

什麼時候用?

什麼時候不該用?

⚠️過度設計警告

現代語言(C#、Python、Java、TypeScript)都內建了 Iterator 機制。除非你在開發自訂集合或需要特殊的走訪邏輯,否則直接用語言內建的 foreachfor...of 就好,不需要手動實作。

執行流程

1

定義 Iterator 介面

宣告 HasNext、Next 等走訪方法

2

定義 IterableCollection 介面

宣告 CreateIterator 方法

3

實作具體集合

儲存元素並建立對應的迭代器

4

實作具體 Iterator

追蹤目前位置,實作走訪邏輯

5

Client 使用迭代器

透過 HasNext/Next 統一走訪,不關心底層結構

流程解讀:Iterator 把「走訪邏輯」從集合中抽離。集合只負責儲存元素和建立 Iterator,Iterator 負責追蹤走訪位置和決定走訪順序。同一個集合可以提供多種 Iterator(正向、反向、篩選),Client 透過統一的 HasNext/Next 介面走訪,完全不需要知道底層是陣列、鏈結串列還是資料庫。

程式碼範例

C# 版本

csharp
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 版本

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 版本

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 版本

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
requests iterator
IterableCollection (Playlist)
creates
Iterator Interface
ConcreteIterator

結構解讀: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 的方法 | | 核心好處 | 統一走訪介面、支援多種走訪策略 | | 代價 | 簡單集合不需要,現代語言大多已內建支援 |

你可能也想看

Template Method PatternState Pattern

按 ← → 鍵切換課程