Menu
CoddyTech

イテレーターパターン

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 76/87。

Iterator Patternは、コレクションの内部構造を公開せずに、要素に順番にアクセスする方法を提供する振る舞いに関するデザインパターンです。配列、連結リスト、ツリーのどれを扱う場合でも、イテレーターを使えば要素を一貫した方法でたどれます。

このパターンは、走査のロジックをコレクション自体から分離します。主な構成要素は2つあります。要素を走査するためのメソッドを定義するIteratorインターフェースと、コレクション用のイテレーターを作成するIterable(または Aggregate)です。

interface Iterator<T> {
    boolean hasNext();
    T next();
}

interface Container<T> {
    Iterator<T> createIterator();
}

class BookShelf implements Container<String> {
    private String[] books;
    private int count = 0;
    
    public BookShelf(int size) {
        books = new String[size];
    }
    
    public void addBook(String book) {
        books[count++] = book;
    }
    
    public Iterator<String> createIterator() {
        return new BookIterator();
    }
    
    private class BookIterator implements Iterator<String> {
        private int index = 0;
        
        public boolean hasNext() {
            return index < count;
        }
        
        public String next() {
            return books[index++];
        }
    }
}

イテレーターは独自の走査状態を保持するため、複数のイテレーターが同じコレクションをそれぞれ独立して走査できます。クライアントは、コレクションがデータをどのように格納しているかを知らなくても、イテレーターを使用できます。

BookShelf shelf = new BookShelf(3);
shelf.addBook("Design Patterns");
shelf.addBook("Clean Code");

Iterator<String> iterator = shelf.createIterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

Javaに組み込まれているIterableインターフェースとIteratorインターフェースは、このパターンに正確に従っています。そのため、Iterableを実装する任意のクラスで拡張forループを使用できます。複数の走査方法を提供したり、複雑な内部構造をクライアントコードから隠したりする必要がある場合、Iteratorパターンは欠かせません。

challenge icon

チャレンジ

簡単

Iterator Patternを使ってプレイリストシステムを作りましょう!曲を保存し、曲の内部での保存方法を公開せずに、順番にたどるためのカスタムイテレーターを提供する音楽プレイリストを作成します。これはIterator Patternの完璧な使用例です。すっきりと統一されたインターフェースを通じて、ユーザーがコレクション内を移動できるようになります。

コードを4つのファイルに分けて作成します。

  • Iterator.java: 2つのメソッドを持つジェネリックなIterator<T>インターフェースを定義します。hasNext()は、要素がまだあるかどうかを示すbooleanを返し、next()は型Tの次の要素を返します。
  • Playlist.java: 曲を保持する集約クラスを作成します。Playlistは、固定容量(コンストラクターに渡されます)のString配列に曲を保存し、追加された曲の数を記録します。プレイリストに曲を追加するaddSong(String song)メソッドを含めます。

    Playlistには、Iterator<String>を返すcreateIterator()メソッドが必要です。Iteratorインターフェースを実装するPlaylistIteratorという名前のprivateな内部クラスを作成して実装します。この内部イテレーターは独自のインデックス位置を保持し、曲の配列をたどって、各曲を順番に返します。

  • Song.java: 曲のタイトルをラップするシンプルなSongクラスを作成します。タイトル(String)を受け取るコンストラクター、getTitle()メソッド、そしてPlaying: [title]を返すtoString()メソッドを持たせます。
  • Main.java: イテレーターシステムを組み合わせます!入力は1つ受け取ります。カンマ区切りの曲名のリストです(例: Bohemian Rhapsody,Stairway to Heaven,Hotel California)。

    曲を10曲まで保存できるPlaylistを作成します。入力を解析し、各曲名をプレイリストに追加します。次にプレイリストからイテレーターを取得し、それを使ってすべての曲をたどり、各曲をSongオブジェクトにラップして出力します(Playing: [title]と表示されます)。

    すべての曲をたどった後、新しい行にPlaylist complete!を出力します。

入力として、カンマ区切りの曲名文字列を1つ受け取ります。

たとえば、入力がYesterday,Imagine,Let It Beの場合、出力は次のようになります。

Playing: Yesterday
Playing: Imagine
Playing: Let It Be
Playlist complete!

Mainクラスは、基盤となる配列の構造を知らずにプレイリストをたどるため、イテレーターのhasNext()メソッドとnext()メソッドを使っています。イテレーターがすべての走査ロジックをカプセル化することで、コレクションの内部表現をクライアントコードから隠しています!

自分で試してみよう

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String input = scanner.nextLine();
        
        // TODO: 10曲を格納できるPlaylistを作成する
        
        // TODO: 入力(カンマ区切り)を解析し、各曲のタイトルをプレイリストに追加する
        
        // TODO: createIterator()を使ってプレイリストからイテレータを取得する
        
        // TODO: イテレータを使ってすべての曲を順にたどる
        // 各曲をSongオブジェクトに格納して出力する
        
        // TODO: すべての曲をたどった後に"Playlist complete!"を出力する
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Javaオンラインコンパイラ