kakke18’s blog

ゆるふわ学生エンジニア

Iterator -1つ1つ数え上げる-

Java言語で学ぶデザインパターン入門 の第1章を参考にしました。

Iteratorパターンとは

本書には、以下のように記載されています。

何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンする処理を行うもの。

簡単に言うと、for文を一般化したような感じですね。 Iterateとは、繰り返すという意味、Iteratorとは、反復子という意味です。 特徴として、数え上げの仕組みがAggregate役の外に置かれています(詳細は後述)。

Iteratorパターンの登場人物

  • Iterator(反復子):順番にスキャンする
  • Aggregate(集合体):Iterator役を作り出す
    f:id:kakke18:20190409013326p:plain
    Iteratorパターンのクラス図

実装

こちらを参考にしてください。

なぜIteratorパターンを使うのか?

以下のコードはIteratorパターンを使ったMainクラスです。

public class Main {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        Iterator it = bookShelf.iterator();
        while (it.hasNext()) {
            Book book = (Book)it.next();
            System.out.println(book.getName());
        }
    }
}

一方、こちらのコードは素直にfor文を回しているMain2クラスです。

public class Main2 {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        for (int i = 0; i < bookShelf.getLength(); i++) {
            Book book = bookShelf.getBookAt(i);
            System.out.println(book.getName());
        }
    }
}

この二つのコードの大きな違いは、ループ部分です。 一見すると、抽象クラスなどややこしいものを使用していないMain2クラスの方が優れているように見えます。 しかし、オブジェクト指向的には、Mainクラスの方が優れた設計であると言えます。

では、なぜIteratorを使うのでしょうか? それは、MainクラスがBookShelfの実装に依存していないからです。

Mainクラスでは、BookShelfクラスのメソッドを使用していません。 しかし、Main2クラスでは、BookShelfクラスのメソッド(getLength()やgetBookAt())を使用しています。 つまり、Main2クラスはBookShelfクラスに依存していることになります。

これの何が問題なのでしょうか? 例えば、BookShelfの実装を配列ではなく、java.util.Vectorを使うように変更したり、 プログラムからではなくDBなど外部から参照するように変更した場合、 Main2クラスでは、ループ部分まで変更する必要があります。 しかし、Mainクラスでは、BookShelfがどのような変更をされようともiteratorメソッドを持っており、 正しいIterator(hasNext()とnext()を実装しているクラスのインスタンス)を返してくれればループ部分の実装を変更する必要がありません。

しかし、Mainクラスを使用した場合でもBookShelfクラスに変更があれば、BookShelgIteratorクラスは変更しなければなりません。 結局、MainクラスでもMain2クラスでも変更する点は多いじゃん!となりますね(笑)

ですが、今回の論点は変更点の多さではなく、依存度についてです。 各クラスの依存度を下げることにより、クラスの再利用化が促進されます。 これこそオブジェクト指向の三大要素の一つであるポリモーフィズムですね。

感想

高専時代にJavaを習って「抽象クラスとかなんで存在してるの...」とか思っていた若かりし自分に教えてあげたい。