Menu
Coddy logo textTech

イテレーターパターン

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ループを使用できます。イテレータパターンは、複数の走査方法を提供する必要がある場合や、複雑な内部構造をクライアントコードから隠蔽する必要がある場合に不可欠です。

challenge icon

チャレンジ

簡単

イテレータパターン(Iterator Pattern)を使用して、プレイリストシステムを構築しましょう!曲を保存し、それらを順番に走査するためのカスタムイテレータを提供する音楽プレイリストを作成します。この際、曲が内部でどのように保存されているかを公開する必要はありません。これはイテレータパターンの完璧なユースケースであり、クリーンで統一されたインターフェースを通じてユーザーがコレクションを操作できるようにします。

コードは4つのファイルに分けて構成します:

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

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

  • Song.java: 曲のタイトルをラップするシンプルな Song クラスを作成します。タイトル(String)を受け取るコンストラクタ、getTitle() メソッド、および "Playing: " + title(結果として 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() メソッドを使用してプレイリストを走査していることに注目してください。イテレータはすべての走査ロジックをカプセル化し、コレクションの内部表現をクライアントコードから隠蔽します!

チートシート

Iterator パターンは、コレクションの要素に、その内部構造を露出させることなく順番にアクセスする方法を提供する振る舞いに関するデザインパターンです。

このパターンは、2つの主要なコンポーネントを使用して、走査ロジックをコレクション自体から分離します。

  • Iterator インターフェース:要素を走査するためのメソッドを定義します
  • Iterable/Container:コレクションのイテレータを作成します

基本構造

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 インターフェースはこのパターンに従っており、拡張 for ループを可能にしています。

自分で試してみよう

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: 入力(カンマ区切り)を解析し、各曲のタイトルを playlist に追加する
        
        // TODO: createIterator() を使用して playlist から iterator を取得する
        
        // TODO: iterator を使用してすべての曲を走査する
        // 各曲について、それを Song オブジェクトでラップして出力する
        
        // TODO: すべての曲を反復処理した後、"Playlist complete!" を出力する
    }
}
quiz icon腕試し

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

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