Iteratorパターン


 ★★★

Iteratorパターン

データ集合から要素を順にアクセスする方法を提供する

プログラミングにおいて、あるデータの集合から各データ要素を順にとりだして利用したいことがよくあります。こういった時に利用できるのがIteratorパターンです。もう少し正確に説明すると、Iteratorパターンは「データの集合を表現するオブジェクト」から、「集合の各要素へ順番にアクセスするアルゴリズム」を切り離すことを目的とするデザインパターンといえます。

Iteratorパターン は、オブジェクトの持つデータ構造を非公開にして、次の2つのメソッドだけを公開するというものです(メソッド名は何でも良いですが、とりあえず Java の Iterator インタフェースに合わせました)。

次のページある?
そのページを開く
まだ残ってる?
ひとつ取り出す
言ってないこと
 まだあるでしょ?
ひとつ白状する

機能メソッド
次のデータがあるかどうかhasNext()
次のデータを取得し、その次に進むnext()

のメソッドは、まだ取得されていないデータが残っていれば true を返し、残ってないなら false を返します。 は、順番にデータを取得して返します。 のメソッドで「データありますか?」と質問し、答えが true なら のメソッドで「データください」と要求するわけです。


では、どうしてIteratorパターンという面倒なものを考える必要があるのでしょう。配列だったら、for 文でくるくる回せばいいじゃないか、という疑問がわいてきます。

Iteratorパターンを使う、その大きな理由は、実装とは切り離して数え上げを行うことができるからです。Iteratorパターンでは、 しか呼び出されません。つまり、実際のデータが配列なのか、リストなのか、スタックなのかといった実装には依存しないということになるからです。実際のデータが配列からリストに変更になっても、ユーザプログラムの を使用しているところは変更しなくてもすみます。



Iteratorは、全体のサイズが把握できないストリームや、一括でデータを返してくるのではなく順次返してくるデータに対して有効です。

例題

本が4冊入っている本棚があります。本棚に入っている本の名前を表示してください。

本は Book クラス、本棚は BookShelf クラスとして定義されています。


実行結果
AAAAA
BBBBB
CCCCC
DDDDD








既存クラス

Book.java
public class Book { private String name; public Book(String name) { this.name = name; } public String getName() { return name; } }

Iteratorパターンを使用しない例

BookShelf.java
public class BookShelf { 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; } public int getCount() { return last; } }
Main.java
public class Main { public static void main(String[] args) { BookShelf bookShelf = new BookShelf(4); bookShelf.appendBook(new Book("AAAAA")); bookShelf.appendBook(new Book("BBBBB")); bookShelf.appendBook(new Book("CCCCC")); bookShelf.appendBook(new Book("DDDDD")); int i = 0; while (i < bookShelf.getCount()) { Book book = bookShelf.getBookAt(i++); System.out.println(book.getName()); } } }

Iteratorパターンを使用した例

BookShelf.java
public class BookShelf { 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; } public int getCount() { return last; } public IIterator<Book> iterator() { return new BookShelfIterator(this); } }
IIterator.java
public interface IIterator<T> { public abstract boolean hasNext(); public abstract T next(); }
BookShelfIterator.java
public class BookShelfIterator implements IIterator<Book> { private BookShelf bookShelf; private int index; public BookShelfIterator(BookShelf bookShelf) { this.bookShelf = bookShelf; this.index = 0; } public boolean hasNext() { return (index < bookShelf.getCount()); } public Book next() { return bookShelf.getBookAt(index++); } }
Main.java
public class Main { public static void main(String[] args) { BookShelf bookShelf = new BookShelf(4); bookShelf.appendBook(new Book("AAAAA")); bookShelf.appendBook(new Book("BBBBB")); bookShelf.appendBook(new Book("CCCCC")); bookShelf.appendBook(new Book("DDDDD")); IIterator<Book> it = bookShelf.iterator(); while (it.hasNext()) { Book book = it.next(); System.out.println(book.getName()); } } }

main の繰り返しの部分で BookShelf の getBookAt() というメソッドを呼んでいます。つまり、main で BookShelf に依存したコーディングがされているということです。もしBookShelf でない別のクラスからデータを取り出すことになったら、メソッドは getBookAt() ではないかもしれません。仕様変更のたびに、ソースを見直さなければなりません。

データがまだあるかどうかは hasNext()、データの取り出しは next() です。

BookShelf クラスに iterator() メソッド、そして、IIterator を継承(インタフェースなので、インプリメント)した BookShelfIterator クラスが追加されています(java.util.Iterator もありますが、ここではあえて使用せず、IIterator を自作しています)。

main からBookShelf インスタンスの iterator() メソッドを呼ぶことによって、BookShelf インスタンスを持った BookShelfIterator インスタンスが生成されます。それが main の it にセットされます。

そして、BookShelfIterator インスタンスのhasNext()を呼ぶことによって、BookShelf インスタンスの getCount() を呼び出しデータの終わりかどうかを判定し、また、next()を呼ぶことによって、getBookAt() を呼び出しデータを取り出しています。