📚

デザインパターン: Iteratorパターン -処理を繰り返す-

2024/05/09に公開

はじめに

GoFの23のデザインパターンがまとめられている「Java言語で学ぶデザインパターン入門」を読んでアウトプットとしてデザインパターンを1つずつ記事としてアウトプットしていきます。
原則的にJavaで実装コード例などを記述していきますが、気になったことや改善点、感想等ありましたらぜひコメントくださると嬉しいです!

23のパターン一覧はこちらから ※随時更新中

Iteratorパターンとは

「Iterate」とは日本語で何かを繰り返す、反復すると言う意味で、オブジェクトの集合体(例えばリストや配列など)を操作するためのデザインパターンです。

集合体の内部構造と、集合体へのアクセス方法を分離することで集合体の内部構造が変更されても、Iteratorのインターフェースを変更することなく、外部からのアクセスを維持することができます。


Iteratorパターンのクラス図

Iteratorパターンを構成する4つのクラス

  • Aggregate(集約)クラス
    Iteratorインスタンスを生成するメソッドを宣言するためのインターフェースを定義します。
  • ConcreteAggregate(具体的な集約)クラス
    Aggregateクラスで定義したインターフェースを実装し、Iteratorインスタンスを生成する具体的なメソッドを実装します。
  • Iterator(反復子)クラス
    要素を1つずつ順番に取り出すためのインターフェースを定義します。
  • ConcreteIterator(具体的な反復子)クラス
    Iteratorクラスで定義したインターフェースを実装し、実際に要素を1つずつ順番に取り出す具体的なメソッドを実装します。

Iteratorパターンを利用するメリット

Iteratorパターンを利用することで以下のようなメリットが得られます。

  • 集合体の具体的な構造を気にする必要がなくなり、コードがよりシンプルで読みやすくなります。
  • Iteratorが外部に公開されるインターフェースとなり、集合体の実装の詳細を隠蔽することができます。
  • 異なる種類の集合体を同じ方法で走査できます。例えば配列、リスト、マップなどさまざまな種類の集合体を走査する際に、同じIteratorインターフェースを再利用できます。

Javaでの実装例

実装のイメージ図

使用するクラス及びインターフェース

名前 解説
Iterable<E> 集合体を表すインターフェース(java.langパッケージ)
実装例ではIterable<Book>として使用
Iterator<E> 処理を繰り返すインターフェース(java.utilパッケージ)
実装例では Iterator <Book>として使用
Book 本を表すクラス
BookShelf 本棚を表すクラス
BookShelfIterator 本棚をスキャンするクラス
Main 動作確認用のクラス
Iterable<E>インターフェース
public interface Iterable<E> {
    public abstract Iterator<E> iterator();
}
Iterator<E>インターフェース
public interface Iterator<E> {
    public abstract boolean hasNext();
    public abstract E next();
}

ここで出てくる2つのメソッドの役割は

1.hasNext()メソッド

次の要素が存在するかを調べる。

2.next()メソッド

次の要素を返す。

ここで一つ注意なのがnext()メソッドは「next」と言う名前に惑わされがちなのですが、戻り値で返すのは現在の要素です。

Bookクラス
public class Book {
    private String naem;

    public setBook(String name) {
        this.name = name;
    }

    public void getName() {
        return name;
    }    
}

Bookクラスは文字通り本を表すクラス。

BookShelfクラス
import java.util.Iterator;

public class BookShelf  implements Iterable<Book> {
    private Book[] book;
    private int last = 0;

    @Override
    public Iterator<Book> iterator() {
        return new BookShelfIterator(this);
    }
    
    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;
    }
}

BookShelfクラスは本棚を表すクラスで、Iterable<Book>インターフェースを実装しています。

BookShelfIteratorクラス
import java.util.Iterator;
import java.util.NoSuchElementException;

public class BookShelfIterator implements Iterator<Book> {
    private BookShelf bookShelf;
    private int index;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        if (index < bookShelf.getLength()) {
            return true;
        } else {
            return false;
        }
    }
    
    @Override
    public Book next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        } 
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
        }
    }
}

BookShelfIteratorはIteartor<Book>インターフェースを実装しており、BookShelfクラスのスキャンを行います。

Mainクラス
import java.util.Iterator;

public class Main {
    public static void main(String args[]) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("いかついJava"));
        bookShelf.appendBook(new Book("かんたんなJava"));
        bookShelf.appendBook(new Book("むずかしいJava"));
        bookShelf.appendBook(new Book("やさしいJava"));

        // 明示定期にIteratorを使う方法
        Iteartor<Book> it = bookShelf.iterator();
        while(it.hasNext()) {
            Book book = it.next();
            System.out.println(book.getName());
        }
        System.out.println();
        // 拡張for文を使う方法
        for (Book book: bookShelf) {
            System.out.println(book.getName())
        }
        System.out.println();
    }    
}

なぜfor文ではなくIteratorパターンを使うのか

実装がどうであれ、Iteratorを使える

なぜIteratorパターンと言う面倒なものを考えるか、配列を使っているならfor文でくるくる回せばいいのではないか。集約オブジェクトの外にわざわざIterator役を作る必要があるのか。
大きな理由は、Iteartorパターンを使うことで実装と切り離して繰り返しを使えるからです。
次のコードを見てみましょう

while(it.hasNext()) {
    Book book = it.next();
    System.out.println(book.getName());
}

ここで使われているのは、hasNextとnextというIteratorのメソッドだけで、BookShelfの実装で使われているメソッドは呼び出されていません。つまりwhileループはBookShelfの実装に依存しないということになります。
これよって、BookShelfの実装を配列からArrayListに変えたとしても上のwhileループは変更しなくても動作します。

デザインパターンはクラスの再利用化を促進するものです。再利用化を促進するとは、クラスを部品として使えるようにすることで1つの部品を修正しても、他の部品の修正が少なく済む、ということです。

まとめ

Iteratorパターンの概要の復習

Iteratorパターンは、集約オブジェクト内部の要素に順番にアクセスする方法を提供し、要素にアクセスする方法を共通化することができます。

このパターンを使用すると、集約オブジェクト内部のデータ構造を隠蔽し、集約オブジェクトの変更に強いコードを書くことができます。

Discussion