はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回は、振る舞いに関するパターンの中のIterator(イテレーター)パターンを解説します。
Javaを使っていればfor-each文で毎日のように無意識に恩恵を受けている、非常に身近なパターンです。
解決したい課題
配列、リスト、木構造、ハッシュマップなど、コレクション(データの集合)には様々な内部構造があります。
もし、コレクションの要素を1つずつ処理するコードを書くたびに、その内部構造(配列なのか、連結リストなのか、木構造なのか)を呼び出し側が意識しなければならないとすると、コレクションの実装を後から変更した際に、それを利用しているすべてのコードに影響が及んでしまいます。
Iteratorパターンは、「集合の要素を順番に取り出す」という操作を、専用のIteratorオブジェクトに切り出すことで、この問題を解決します。
呼び出し側は、コレクションの内部構造を一切知らなくても、共通の方法で要素を1つずつ走査できます。
クラス構成
- Iterator(イテレーター): 要素を1つずつ取り出すための共通インターフェース(「次の要素があるか」「次の要素を取得する」)
- ConcreteIterator(具体的なイテレーター): 特定のコレクションに対応した、具体的なIteratorの実装
- Aggregate(集合): Iteratorを生成するメソッドを持つ、コレクション側の共通インターフェース
- ConcreteAggregate(具体的な集合): 実際にデータを保持する、具体的なコレクションクラス
Javaでの実装例
「本棚(BookShelf)」から「本(Book)」を1冊ずつ取り出す例を、あえてJava標準のIterable/Iteratorを使わず、パターンの構造がわかる形で自作実装してみます。
Iterator:共通インターフェース
public interface MyIterator<T> {
boolean hasNext();
T next();
}
Aggregate:集合(コレクション)側の共通インターフェース
public interface MyAggregate<T> {
MyIterator<T> createIterator();
}
要素となるBookクラス
public class Book {
private final String title;
public Book(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
}
ConcreteAggregate:具体的なコレクションクラス
import java.util.ArrayList;
import java.util.List;
public class BookShelf implements MyAggregate<Book> {
private final List<Book> books = new ArrayList<>();
public void addBook(Book book) {
books.add(book);
}
public Book getBookAt(int index) {
return books.get(index);
}
public int getBookCount() {
return books.size();
}
@Override
public MyIterator<Book> createIterator() {
return new BookShelfIterator(this);
}
}
ConcreteIterator:BookShelf専用のイテレーター
public class BookShelfIterator implements MyIterator<Book> {
private final BookShelf bookShelf;
private int currentIndex = 0;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
}
@Override
public boolean hasNext() {
return currentIndex < bookShelf.getBookCount();
}
@Override
public Book next() {
Book book = bookShelf.getBookAt(currentIndex);
currentIndex++;
return book;
}
}
利用側のコード
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf();
bookShelf.addBook(new Book("リーダブルコード"));
bookShelf.addBook(new Book("デザインパターン入門"));
bookShelf.addBook(new Book("Effective Java"));
// 利用側は、BookShelfの内部がListであることを知らなくても走査できる
MyIterator<Book> iterator = bookShelf.createIterator();
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println(book.getTitle());
}
}
}
実行結果:
リーダブルコード
デザインパターン入門
Effective Java
利用側のコードは、BookShelfが内部でArrayListを使っているのか、配列を使っているのか、あるいは連結リストなのかを一切知りません。
hasNext()とnext()という共通のインターフェースだけを使って、走査を行っています。
将来BookShelfの内部実装をArrayListから別のデータ構造に変更しても、BookShelfIteratorの内部だけを修正すればよく、利用側のコードには影響しません。
Java標準ライブラリでの実例
実は、この考え方はJava言語自体に深く組み込まれています。
java.util.Iteratorインターフェースとjava.lang.Iterableインターフェースがそれに当たります。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
List<String> titles = new ArrayList<>(List.of("リーダブルコード", "Effective Java"));
// Iterableを実装したクラスなら、for-each文が使える(内部的にはIteratorが使われている)
for (String title : titles) {
System.out.println(title);
}
// 上記のfor-each文は、実質的に以下のコードと同じ
Iterator<String> it = titles.iterator();
while (it.hasNext()) {
String title = it.next();
System.out.println(title);
}
自作クラスにIterable<T>を実装し、iterator()メソッドを提供すれば、そのクラスもfor-each文で走査できるようになります。
これは、まさにIteratorパターンをJava言語のシンタックスシュガー(糖衣構文)として組み込んだものと言えます。
使用場面
- 自作のコレクションクラスを、利用側に統一的な方法で走査させたい場合
- コレクションの内部データ構造(配列・リスト・木構造など)を隠蔽し、将来の実装変更の影響を抑えたい場合
- 同じコレクションに対して、異なる走査方法(順方向・逆方向・条件を満たす要素だけ、等)を複数提供したい場合
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | コレクションの内部構造を、利用側が意識せずに要素を走査したい |
| 実現方法 | 「要素を順番に取り出す」処理を、専用のIteratorオブジェクトに切り出す |
| メリット | コレクションの内部実装を変更しても、利用側のコードに影響しない |
| Java標準 |
Iterable/Iteratorインターフェースとして言語レベルで組み込まれている(for-each文) |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


