← 一覧に戻る
振る舞いに関するパターン

Iterator

概要

走査と操作を分ける 一つの走査に対して 複数の操作を行えたり

マンガでわかる Iterator

マンガでわかる Iterator #デザインパターン - Qiita

でざぱたんで覚える Iterator

ちびキャラは「イテレータたん」。荷物の中身も使い道も気にしない黒猫の運び屋。「順に届ける」ことだけを請け負うから、どんな荷(操作)とも組める——ループの添字を隠すことよりも、走査(順に辿る)と操作(要素に何をするか)の分離こそがIteratorの真価、と本書は強調する。一つの走査に複数の操作を、複数の走査に一つの操作を自由に組み合わせられる。

出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.158〜)

意図

Iterableな構造はたくさんある。IOとかMAPとかリンクリストとか。それらに対して

for($i = 0; $i < $length; $i ++ ){
  ...
}

必ずしもこのように走査できるわけではない。

そこで「関心の分離」。反復可能という概念だけ抽出して、ロジックは個々のデータ構造にまかせる。

クラス図

Iterator UML class diagram - Iterator パターン - Wikipedia

このサイトの実装(本棚の走査)での対応関係:

classDiagram
  class Aggregate {
    <<interface>>
    +iterator() Iterator
  }
  class Iterator {
    <<interface>>
    +hasNext() bool
    +next() Object
  }
  class BookShelf {
    -books Book[]
    -last int
    +getBookAt(index) Book
    +appendBook(book)
    +getLength() int
    +iterator() Iterator
  }
  class BookShelfIterator {
    -bookShelf BookShelf
    -index int
    +hasNext() bool
    +next() Object
  }
  class Book {
    -name String
    +getName() String
  }
  Aggregate <|.. BookShelf
  Iterator <|.. BookShelfIterator
  BookShelf o-- Book
  BookShelf ..> BookShelfIterator : creates
  BookShelfIterator --> BookShelf : bookShelf

留意点

  • モダン言語なら標準で搭載されていることが多い機能なので、自分で書くことを目的にする必要はない。
  • あくまで名前を持つ概念の意味を理解して共有することが目的。
  • それはそれとしてIteratorは偉大なのでよく味わうべき

やり方

イテレータには以下を実装する

  1. 最初の要素に戻す
  2. 現在位置が有効かどうか
  3. 現在の要素を返す
  4. 次の要素に進める
  5. 現在のキーを返す
Java
BookShelfIterator.java
public class BookShelfIterator implements Iterator {
    private BookShelf bookShelf;
    private int index;
    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }
    public boolean hasNext() {
        if (index < bookShelf.getLength()) {
            return true;
        } else {
            return false;
        }
    }
    public Object next() {
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
    }
}
Aggregate.java
public interface Aggregate {
    public abstract Iterator iterator();
}
Iterator.java
public interface Iterator {
    public abstract boolean hasNext();
    public abstract Object next();
}
BookShelf.java
public class BookShelf implements Aggregate {
    private Book[] books;
    private int last = 0;
    public BookShelf(int maxsize) {
        this.books = new Book[maxsize];
    }
    public Book getBookAt(int index) {
        return books[index];
    }
    public void appendBook(Book book) {
        this.books[last] = book;
        last++;
    }
    public int getLength() {
        return last;
    }
    public Iterator iterator() {
        return new BookShelfIterator(this);
    }
}
Book.java
public class Book {
    private String name;
    public Book(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
Main.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        Iterator it = bookShelf.iterator();
        while (it.hasNext()) {
            Book book = (Book)it.next();
            System.out.println(book.getName());
        }
    }
}
Go

Aggregate/Iteratorともにinterfaceで表現。Java版のIterator#next()はGenerics以前の Javaの制約でObjectを返し呼び出し側でキャストするが、Goには型消去された共通の親が無いため Next() anyにして呼び出し側で型アサーションする形にした(Go 1.23以降のiter.Seq (range-over-func)という言語標準の反復子機構はあえて使わず、hasNext()/next()という Java版そのままの構造で移植している)。

実行: go run ./GoF/patterns/Iterator/go

$ go run ./GoF/patterns/Iterator/go
aggregate.go
package main

// 実行: go run ./GoF/patterns/Iterator/go

// Aggregate はJava版 Aggregate.java 相当。
// 「自分用のIteratorを作れる」ことだけを約束するインタフェース。
//
// Go の interface は暗黙実装(implements を書かない)。BookShelf がこれを満たす。
type Aggregate interface {
	Iterator() Iterator
}
iterator.go
package main

// 実行: go run ./GoF/patterns/Iterator/go

// Iterator はJava版 Iterator.java 相当。BookShelfIterator がこれを実装するConcreteIterator。
//
// Java版はGenerics以前のJavaの制約でnext()がObjectを返し、呼び出し側でキャストする
// (Main.javaの (Book)it.next())。Goには型消去されたObject相当の共通の親が無いので、
// Java版に一番忠実に対応させるならnext()の戻り値をany(interface{})にして呼び出し側で
// 型アサーションする、という形になる。
//
// Go 1.23以降にはrange-over-func(iter.Seq)という言語標準の反復子機構があるが、
// ここではあえて使わない。比較サイトの趣旨はJava版の構造をそのまま多言語に映すこと
// なので、hasNext()/next()という明示的なインタフェースをそのままGoのinterfaceとして
// 移植する。
type Iterator interface {
	HasNext() bool
	Next() any
}
book.go
package main

// 実行: go run ./GoF/patterns/Iterator/go

// Book はJava版 Book.java 相当。
type Book struct {
	name string
}

// NewBook はコンストラクタ相当。
func NewBook(name string) *Book {
	return &Book{name: name}
}

// Name は本の名前を得る。
func (b *Book) Name() string {
	return b.name
}
book_shelf.go
package main

// 実行: go run ./GoF/patterns/Iterator/go

// BookShelf はJava版 BookShelf.java 相当。AggregateのConcreteAggregate。
//
// Java版はコンストラクタでmaxsizeを受け取り固定長配列を確保するが、
// Goではsliceで足りるので、Composite/goのDirectoryと同様に可変長で持つ
// (maxsizeはappendの容量ヒントとしてだけ使う)。
type BookShelf struct {
	books []*Book
}

// NewBookShelf はコンストラクタ相当。
func NewBookShelf(maxsize int) *BookShelf {
	return &BookShelf{books: make([]*Book, 0, maxsize)}
}

// BookAt は指定indexの本を得る。
func (s *BookShelf) BookAt(index int) *Book {
	return s.books[index]
}

// AppendBook は本棚の末尾に本を追加する。
func (s *BookShelf) AppendBook(book *Book) {
	s.books = append(s.books, book)
}

// Length は本棚にある本の冊数を返す。
func (s *BookShelf) Length() int {
	return len(s.books)
}

// Iterator はJava版 BookShelf.iterator() 相当。BookShelfIteratorを生成して返す。
func (s *BookShelf) Iterator() Iterator {
	return NewBookShelfIterator(s)
}
book_shelf_iterator.go
package main

// 実行: go run ./GoF/patterns/Iterator/go

// BookShelfIterator はJava版 BookShelfIterator.java 相当。IteratorのConcreteIterator。
type BookShelfIterator struct {
	bookShelf *BookShelf
	index     int
}

// NewBookShelfIterator はコンストラクタ相当。
func NewBookShelfIterator(bookShelf *BookShelf) *BookShelfIterator {
	return &BookShelfIterator{bookShelf: bookShelf, index: 0}
}

// HasNext は次の要素があるとき true。
func (it *BookShelfIterator) HasNext() bool {
	return it.index < it.bookShelf.Length()
}

// Next は現在位置の本を返し、内部indexを1つ進める。
func (it *BookShelfIterator) Next() any {
	book := it.bookShelf.BookAt(it.index)
	it.index++
	return book
}
main.go
package main

import "fmt"

// 実行: go run ./GoF/patterns/Iterator/go
//
// Java版Main.javaと同じ4冊を本棚に並べ、Iteratorで順番に取り出して表示する。
func main() {
	bookShelf := NewBookShelf(4)
	bookShelf.AppendBook(NewBook("Around the World in 80 Days"))
	bookShelf.AppendBook(NewBook("Bible"))
	bookShelf.AppendBook(NewBook("Cinderella"))
	bookShelf.AppendBook(NewBook("Daddy-Long-Legs"))

	it := bookShelf.Iterator()
	for it.HasNext() {
		// Java版の (Book)it.next() と同じ、呼び出し側での型アサーション(ダウンキャスト)。
		book := it.Next().(*Book)
		fmt.Println(book.Name())
	}
}
PHP
index.php
<?php
ini_set("display_errors", "1");



/**
 * 走査と操作を分ける
 * 一つの走査に対して
 * 複数の操作を行えたり
 */


/**
 * 本
 */
class Book
{
  private $name;

  public function __construct($name)
  {
    $this->name = $name;
  }
  public function getName()
  {
    return $this->name;
  }
}
/**
 * 自前でイテレータのインスタンスを用意
 * イテレータに必要なのは
 * 1. 最初の要素に戻す
 * 2. 現在位置が有効かどうか
 * 3. 現在の要素を返す
 * 4. 次の要素に進める
 * 5. 現在のキーを返す
 */

interface Iteratable
{
  public function rewind();
  public function valid();
  public function current();
  public function next();
  public function key();
}

/**
 * 本棚
 */

class BookShelf
{

  private $bookShelfIterator;

  public function __construct($bookShelfIterator)
  {
    $this->bookShelfIterator = $bookShelfIterator;
  }
  /**
   * 本棚の本をforで数え上げ
   * 一冊ずつfunctionの引数にして実行
   * 
   * @param function $function 
   */

  public function run($function)
  {
    for (
      $this->bookShelfIterator->rewind();
      $current = $this->bookShelfIterator->current();
      $current = $this->bookShelfIterator->next()
    ) {
      $function($current);
    }
  }
}


/**
 * 本棚のイテレータ
 * 
 */


class BookShelfIterator  implements Iteratable
{
  private $bookList = array();
  private $index = 0;

  public function __construct($bookShelf)
  {
    $this->bookList = $bookShelf;
  }

  /**
   * 現在位置より抱えているほんの数の方が大きいときtrue
   */
  public function valid()
  {
    return count($this->bookList) > $this->index;
  }

  /***
   * 初期位置に戻す
   */

  public function rewind()
  {
    $this->index = 0;
  }


  /**
   * 現在位置の本を返す
   * @return Book
   */

  public function current()
  {
    return $this->bookList[$this->index];
  }

  /**
   * 要素を次に進める
   * @return int
   */

  public function next()
  {
    return $this->index++;
  }

  /**
   * 現在のキーを返す
   * @return int
   */
  public function key()
  {
    return $this->index;
  }
}


print "自前イテレータ <br>";
$bookShelf = new BookShelf(new BookShelfIterator(
  array(
    new Book("本1"),
    new Book("本2"),
    new Book("本3"),
  )
));
// PHPって無名関数あったのか...
$bookShelf->run(
  function ($book) {
    print $book->getName() . "<br>";
  }
);


class BookShelfIterator2 extends BookShelfIterator implements Iterator
{
}

print "標準イテレータ <br>";
$bookShelfIterator2 = new BookShelfIterator2(array(
  new Book("本1"),
  new Book("本2"),
  new Book("本3"),

));
// iteratorを実装しているので
// foreachできる
// arrayをiteratorでwrapしている
foreach ($bookShelfIterator2 as $key => $book) {
  print $book->getName() . "<br>";
}
TypeScript

Aggregate<T>/Iterator<T>というJava版そのままのインタフェース構造に、TypeScriptの Genericsで型を付けた(Java版のObjectキャストが不要になる)。Iterable<T>/ Symbol.iteratorというfor-ofと統合された言語標準の反復子機構はあえて使っていない。

実行: npx tsx GoF/patterns/Iterator/typescript/main.ts

$ npx tsx GoF/patterns/Iterator/typescript/main.ts
aggregate.ts
// Iterator パターン: 集合体層 (Java版の Aggregate.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Iterator } from "./iterator";

// Aggregate: 「自分用のIteratorを作れる」ことだけを約束するインタフェース。
export interface Aggregate<T> {
  iterator(): Iterator<T>;
}
iterator.ts
// Iterator パターン: イテレータ層 (Java版の Iterator.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版のIterator#next()はGenerics以前のJavaの制約でObjectを返し、呼び出し側で
// キャストしている(Main.javaの (Book)it.next())。TypeScriptにはGenericsがあるので、
// ここではIterator<T>としてBookをそのまま返せるようにし、呼び出し側のキャストを
// 不要にした。
//
// TypeScriptにはIterable<T>/Symbol.iteratorというfor-ofと統合された言語標準の反復子
// 機構もあるが、あえて使わない。比較サイトの趣旨はJava版の構造をそのまま多言語に映す
// ことなので、hasNext()/next()という明示的なメソッドを持つインタフェースとして
// Java版をそのまま移植する。
export interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
}
book.ts
// Book層 (Java版の Book.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

export class Book {
  constructor(private readonly name: string) {}

  getName(): string {
    return this.name;
  }
}
book_shelf.ts
// BookShelf層 (Java版の BookShelf.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版はコンストラクタでmaxsizeを受け取り固定長配列を確保するが、TypeScriptでは
// 配列が可変長なので、Composite版のDirectoryと同様にmaxsize引数を外している。

import { Aggregate } from "./aggregate";
import { Book } from "./book";
import { BookShelfIterator } from "./book_shelf_iterator";
import { Iterator } from "./iterator";

// BookShelf: AggregateのConcreteAggregate。
export class BookShelf implements Aggregate<Book> {
  private readonly books: Book[] = [];

  getBookAt(index: number): Book {
    return this.books[index];
  }

  appendBook(book: Book): void {
    this.books.push(book);
  }

  getLength(): number {
    return this.books.length;
  }

  // Java版 BookShelf.iterator() 相当。BookShelfIteratorを生成して返す。
  iterator(): Iterator<Book> {
    return new BookShelfIterator(this);
  }
}
book_shelf_iterator.ts
// BookShelfIterator層 (Java版の BookShelfIterator.java 相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Book } from "./book";
import { BookShelf } from "./book_shelf";
import { Iterator } from "./iterator";

// BookShelfIterator: IteratorのConcreteIterator。
export class BookShelfIterator implements Iterator<Book> {
  private index = 0;

  constructor(private readonly bookShelf: BookShelf) {}

  hasNext(): boolean {
    return this.index < this.bookShelf.getLength();
  }

  next(): Book {
    const book = this.bookShelf.getBookAt(this.index);
    this.index++;
    return book;
  }
}
main.ts
// Iterator パターン: 本棚から本を順番に取り出す (Java版Main.javaと同じお題)
//
// 実行: npx tsx GoF/patterns/Iterator/typescript/main.ts
//
// Java版と同じ4冊を本棚に並べ、Iteratorで順番に取り出して表示する。

import { Book } from "./book";
import { BookShelf } from "./book_shelf";

function main(): void {
  const bookShelf = new BookShelf();
  bookShelf.appendBook(new Book("Around the World in 80 Days"));
  bookShelf.appendBook(new Book("Bible"));
  bookShelf.appendBook(new Book("Cinderella"));
  bookShelf.appendBook(new Book("Daddy-Long-Legs"));

  const it = bookShelf.iterator();
  while (it.hasNext()) {
    const book = it.next();
    console.log(book.getName());
  }
}

main();
Python

Aggregate/IteratorともにABCで表現し、Java版のhasNext()/next()has_next()/next()としてそのまま移植した。Pythonには__iter__/__next__ プロトコル(for文と統合された言語標準の反復子機構)があるが、Java版の構造をそのまま 比較できるようにあえて使っていない。

実行: python3 GoF/patterns/Iterator/python/main.py

$ python3 GoF/patterns/Iterator/python/main.py
aggregate.py
"""Iterator パターン: 集合体層 (Java版の Aggregate.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from abc import ABC, abstractmethod

from iterator import Iterator


class Aggregate(ABC):
    """「自分用のIteratorを作れる」ことだけを約束する抽象基底クラス。"""

    @abstractmethod
    def iterator(self) -> Iterator: ...
iterator.py
"""Iterator パターン: イテレータ層 (Java版の Iterator.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。

Java版のIterator#next()はGenerics以前のJavaの制約でObjectを返し、呼び出し側で
キャストしている(Main.javaの (Book)it.next())。PythonにはJavaのfor文と統合された
__iter__/__next__プロトコル(StopIteration送出でfor文が止まる)もあるが、ここでは
Java版のAggregate/Iteratorインタフェース構造をそのまま素直に移植する方針のため、
あえて使わない。has_next()/next()という明示的なメソッドを持つIteratorクラスとして書く
(Pythonは動的型付けなので、next()の戻り値をJava版のようにAnyでキャストし直す必要はない
- BookShelfIteratorはより具体的にBookを返す)。
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any


class Iterator(ABC):
    @abstractmethod
    def has_next(self) -> bool: ...

    @abstractmethod
    def next(self) -> Any: ...
book.py
"""Book層 (Java版の Book.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations


class Book:
    def __init__(self, name: str) -> None:
        self._name = name

    def get_name(self) -> str:
        return self._name
book_shelf.py
"""BookShelf層 (Java版の BookShelf.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。

Java版はコンストラクタでmaxsizeを受け取り固定長配列を確保するが、Pythonではlistで
足りるので、Composite版のDirectoryと同様にmaxsize引数を外している。
"""

from __future__ import annotations

from aggregate import Aggregate
from book import Book
from book_shelf_iterator import BookShelfIterator
from iterator import Iterator


class BookShelf(Aggregate):
    """AggregateのConcreteAggregate。"""

    def __init__(self) -> None:
        self._books: list[Book] = []

    def get_book_at(self, index: int) -> Book:
        return self._books[index]

    def append_book(self, book: Book) -> None:
        self._books.append(book)

    def get_length(self) -> int:
        return len(self._books)

    def iterator(self) -> Iterator:
        """Java版 BookShelf.iterator() 相当。BookShelfIteratorを生成して返す。"""
        return BookShelfIterator(self)
book_shelf_iterator.py
"""BookShelfIterator層 (Java版の BookShelfIterator.java 相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from book import Book
from iterator import Iterator

if TYPE_CHECKING:
    # book_shelf.py が本モジュールを import するため、実行時 import は循環参照になる。
    # `from __future__ import annotations` により型注釈は文字列として遅延評価されるので、
    # 型チェッカ用にのみ import すれば実行時循環は起きない。
    from book_shelf import BookShelf


class BookShelfIterator(Iterator):
    """IteratorのConcreteIterator。"""

    def __init__(self, book_shelf: BookShelf) -> None:
        self._book_shelf = book_shelf
        self._index = 0

    def has_next(self) -> bool:
        return self._index < self._book_shelf.get_length()

    def next(self) -> Book:
        book = self._book_shelf.get_book_at(self._index)
        self._index += 1
        return book
main.py
"""Iterator パターン: 本棚から本を順番に取り出す (Java版Main.javaと同じお題)

実行: python3 main.py
      (もしくはリポジトリルートから python3 GoF/patterns/Iterator/python/main.py)

Java版と同じ4冊を本棚に並べ、Iteratorで順番に取り出して表示する。
"""

from __future__ import annotations

from book import Book
from book_shelf import BookShelf


def main() -> None:
    book_shelf = BookShelf()
    book_shelf.append_book(Book("Around the World in 80 Days"))
    book_shelf.append_book(Book("Bible"))
    book_shelf.append_book(Book("Cinderella"))
    book_shelf.append_book(Book("Daddy-Long-Legs"))

    it = book_shelf.iterator()
    while it.has_next():
        book = it.next()
        print(book.get_name())


if __name__ == "__main__":
    main()