DPDesign Pattern Quest
#16 / 23
B振る舞いDIFF

イテレータ

Iterator

本のしおり。中身がリストでも木でもデータベースでも、『次のページめくって』だけで読み進められる。

Intent · 目的

コレクションの内部表現を晒さずに、要素を順に取り出すための窓口を提供する。利用側は `hasNext()` と `next()` の2つだけ知っていれば、配列だろうが連結リストだろうが二分木だろうが同じように回せる。

Motivation · 動機

「リストの中身を順に処理してください」── これは簡単。でも『二分木を順に』『DBから1件ずつ』『無限に続く乱数列を最初の10個だけ』だとどうか? それぞれに別のループ書法を要求されたら、利用側は内部構造の知識を背負わされる。Iterator は『どんな集合でも、しおりさえあれば順に読める』を保証する標準フォーマット。今や `for-of` や `for-each` という形で各言語に組み込まれているけれど、それは本パターンが空気のように普及した結果。

適用場面

  • 1コレクションの内部実装に依存せず、走査だけを抽象化したい場面。
  • 21つの集合に複数種類の走査(順方向 / 逆方向 / フィルタ付き)を提供したい場合。
  • 3巨大コレクションや無限列を遅延的に扱いたい場合。

概念図

サンプルコード

class Range implements Iterable<number> {
  constructor(private from: number, private to: number) {}
  [Symbol.iterator](): Iterator<number> {
    let cur = this.from;
    const to = this.to;
    return {
      next(): IteratorResult<number> {
        return cur < to
          ? { value: cur++, done: false }
          : { value: undefined as any, done: true };
      }
    };
  }
}

for (const x of new Range(1, 5)) console.log(x);
Pros · メリット
  • 中身が何だろうが、利用側は同じループ構文で書ける。
  • 順方向・逆方向・フィルタ付きなど、走査者を複数共存させられる。
  • 言語標準の for-each / for-of に乗るので、見た目もシンプル。
Cons · デメリット
  • 走査中に元のコレクションを変更すると整合性が壊れる(Java の `ConcurrentModificationException` は通る門)。
  • イテレータ生成のオーバーヘッドが、超ホットなコードでは効いてくることがある。
  • 1度使い切ると再利用できないイテレータが多く、巻き戻したい時に困る。

関連パターン

⚔ ready to test

理解度を確かめる時間だ

3 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →