デザインパターンの中でももっとも理解がしやすく、また利用価値も高いものでIteratorパターンがあります。
Iteratorパターンは数え上げのためのパターンです。
顧客管理では顧客をあいうえお順に並べたり取引日時でソートをかけたりすることもありますし、ゲーム制作であればキャラクターの並び、アイテムの並びなど複数の集合に対して全体をスキャンするといったことは頻繁に登場します。
デザインパターンの触りとしてではなく抽象クラスやインターフェイスの勉強としても利用できます。
Contents
イテレータパターンとは
実際に何かを表示する場合には並び順が重要になります。
追加した順番や割り振られた番号順、あいうえお順などなど。
ゲームをよくされる人であればアイテムの並び順がユーザビリティに大きく影響することをよく知っているかと思います。
このイテレータパターンはオブジェクトの集合に対して、順番に指し示し全体をスキャンするために利用されるものです。
イテレータパターンの説明によく用いられる、本クラスと本棚クラスを例に見ていきましょう。
イテレータパターンの実装
ここでは以下のクラス・インターフェイスを作成します。
Aggregateインターフェイス
集合を表すインターフェイス。Iteratorインターフェイスの作成を宣言します
Iteratorインターフェイス
ループ変数のような役割を果たすインターフェイス。
次の要素があるかどうかを返すhasNextメソッドと、次の要素を指し示すnextメソッドを宣言します。
Bookクラス
1冊の本を表すクラス。説明を簡単にするため本の名前を得るメソッドだけを定義します。
BookShelfクラス
本棚クラス。本の集合体として扱うのでAggregateインターフェイスを実装します。
BookShelfIteratorクラス
BookShelfクラスをスキャンするするクラス。Iteratorインターフェイスを実装します。
どの本棚をスキャンするかはコンストラクタで指定できるようにしています。
Mainクラス
サンプルコードを実際に動作させるクラス。
サンプルコードのクラス図
上記の説明の補足としてクラス図も確認しておきましょう。
小難しいことはこれくらいにして、実際にコーディングしてみましょう。
コーディング
Aggregateインターフェイス
0 1 2 3 4 5 6 7 |
public interface Aggregate { /** * 集合体に対応するイテレータを1つ生成する。 * 集合体を数え上げたい、スキャンしたい、1つずつ調べていきたい場合に、Iteratorインターフェイスを実装したクラスのインスタンスを1つ生成する。 * @return */ public abstract Iterator iterator(); } |
Iteratorインターフェイス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
public interface Iterator { /** * 次の要素が存在するかどうかを調べる * @return 次の要素があればTRUE、次の要素がなければFALSE */ public abstract boolean hasNext(); /** * 次の要素を返す * 次回next()が呼ばれたときにきちんと次の要素を返すように内部状態を次に進めておく必要がある。 * @return */ public abstract Object next(); } |
Bookクラス
0 1 2 3 4 5 6 7 8 9 10 |
public class Book { String name; public Book(String name) { this.name = name; } public String getName() { return name; } } |
BookShelfクラス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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); } } |
BookShelfIteratorクラス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public class BookShelfIterator implements Iterator { private BookShelf bookShelf; // スキャンする本棚 private int index; // 本を指す添え字 public BookShelfIterator(BookShelf bookShelf) { this.bookShelf = bookShelf; this.index = 0; } /** * 次の本があるかどうかは、indexが本棚の本の冊数(bookShelf.getLength()の値)よりも小さいかどうかで判定する。 */ public boolean hasNext() { if (index < bookShelf.getLength()) { return true; } else { return false; } } /** * 戻り値として返すべき本を一時変数に確保し、indexを一つ進める。(for文のインクリメント部に相当) */ public Object next() { Book book = bookShelf.getBookAt(index); index++; return book; } } |
Mainクラス
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Main { public static void main(String[] args) { BookShelf bookShelf = new BookShelf(4); // 4冊入る本棚を生成 bookShelf.appendBook(new Book("A")); bookShelf.appendBook(new Book("B")); bookShelf.appendBook(new Book("C")); bookShelf.appendBook(new Book("D")); Iterator it = bookShelf.iterator(); while (it.hasNext()) { Book book = (Book)it.next(); System.out.println(book.getName()); } } } |
イテレータパターンがなぜ必要なのか
なぜ本棚が持つ本をループで回すだけなのにこんな面倒な実装をしなければならないのでしょう?
重要なヒントとなるのはMainクラスの以下の部分です。
0 1 2 3 |
while (it.hasNext()) { Book book = (Book)it.next(); System.out.println(book.getName()); } |
ループの部分ではBookShelfクラスのメソッドは使用されていません。
つまりBookShelfの実装には依存しないのです。
例えば本クラスを配列で持つことを止め、Vectorクラスを利用するように修正しても正しいIteratorを返しさえすれば上記のループは全く変更する必要がありません。
デザインパターンの根底にあるクラスの再利用化という観点からみても大きな利点となります。
まとめ
Iteratorパターンは簡単に実装できますし、デザインパターンの触りとしても大変すぐれています。
今回は順番にアクセスするだけの単純なものですが、AggregateインターフェイスにreverseIteratorなどを宣言すれば逆順にスキャンするようなものも簡単に作れます。
サンプルプログラムを参考に配列をArrayListに変更してみたり、reverseIteratorを実装してみてください。