daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

Javaでデザインパターンを学ぶ:Iteratorパターン

Javaによるデザインパターンの4回目です!

教材は、参考文献の「Java言語で学ぶデザインパターン入門第3版」のサンプルコードを使わせて頂きます。

今回は、Iteratorパターンです。

実は、第1回で書いていたんですが、途中で疑問が出てきて、解決できなくて、下書きに眠ってました(笑)

そのあたりも話せたらと思います。

では、やっていきましょう!

参考文献

参考文献のサンプルプログラムのダウンロード

www.hyuki.com

はじめに

「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。

Javaでデザインパターンの記事一覧

先に、23種類のデザインパターンを示します。

参考サイト:デザインパターン (ソフトウェア) - Wikipedia

デザインパターン一覧
◆生成に関するパターン
 ・Abstract Factory:関連するインスタンスを状況に応じて、適切に生成する方法を提供する
 ・Builder:複合化されたインスタンスの生成過程を隠蔽する。
 ・Factory Method:生成されるインスタンスに依存しない、インスタンスの生成方法を提供する
 ・Prototype:同様のインスタンスを生成するために、原型のインスタンスを複製する
 ・Singleton:あるクラスについて、インスタンスが1つしか存在しないことを保証する
◆構造に関するパターン
 ・Adapter:元々関連性のない2つのクラスを接続するクラスを作る
 ・Bridge:クラスと呼び出し側の間の橋渡しをするクラスで、実装を隠蔽する
 ・Composite:再帰的な構造を表現する
 ・Decorator:あるインスタンスに対し、動的に付加機能を追加する
 ・Facade:複数のサブシステムの窓口となる共通のインタフェースを提供する
 ・Flyweight:多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす
 ・Proxy:共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる
◆振る舞いに関するパターン
 ・Chain of Responsibility:イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されていくようにする
 ・Command:複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切り替えを実現する
 ・Interpreter:構文解析のために、文法規則を反映するクラス構造を作る
 ・Iterator:複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する
 ・Mediator:オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする
 ・Memento:データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする
 ・Observer:インスタンスの変化を他のインスタンスから監視できるようにする
 ・State:オブジェクトの状態を変化させることで、処理内容を変えられるようにする
 ・Strategy:データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切り替えを容易にする
 ・Template Method:アルゴリズムは抽象クラスで、処理内容はサブクラスで定義する
 ・Visitor:データ構造を保持するクラスと、それに対して処理を行うクラスを分離する

今回は、「Iteratorパターン」をやっていきます!

サンプルコードの理解

Iteratorパターンは、集約オブジェクト(集合体)に対するアクセス方法を提供する、とあります。集約オブジェクトとは、何らかの物を格納するリストのようなもののことです。

これだけ聞いても、何が嬉しいのか分からないですね。なので、早速サンプルコードを見ていくことにします。

今回のPlantUMLのクラス図

PlantUMLで作成したクラス図のコードと画像ファイルです。

Iteratorインタフェースを実装したBookShelfIteratorクラス(イテレータ)と、Iterableインターフェースを実装したBookShelfクラス(本棚)と、Bookクラス(本)の3つの具象クラスで構成されています

@startuml
class Book {
    - String name
    + String getName()
}

interface Iterator<E> {
    + {abstract} boolean hasNext()
    + {abstract} E next()
}

class BookShelfIterator {
    - BookShelf bookShelf
    - int index
    + boolean hasNext()
    + Book next()
}
Iterator <|.. BookShelfIterator
BookShelf -o BookShelfIterator

interface Iterable<E> {
    + {abstract} Iterator<E> iterator
}
Iterable -> Iterator : create >

class BookShelf {
    - Book[] books
    - int last
    + Book getBookAt()
    + void appendBook()
    + int getLength()
    + Iterator<Book> iterator()
}
Iterable <|.. BookShelf
BookShelf o-- Book
note left of BookShelf::iterator
  return new BookShelfIterator(this)
end note
@enduml

今回のクラス図
今回のクラス図

Mainクラス(使う側のクラス)

まずは、Iteratorパターンが、どう使われるのかを見るために、Mainクラスを見ていきます。

分かりやすくなると思うので、先に実行結果も示します。

import java.util.Iterator;

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を使う方法
        Iterator<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();
    }
}

実行結果

実行結果
実行結果

まず、BookShelfクラス(本棚)のインスタンスを作ります。次に、様々なタイトルの4冊の本のインスタンスを作り、本棚に追加していきます。

最後に、本棚のIteratorを使って、順番に本を取り出し、本のタイトル(名前)を表示していきます。最初は、通常通りIteratorを使う方法を用いて、その後、拡張for文を使って実行しています。

今回のIteratorパターンは、本棚から順番に本を取り出す方法を提供しています。Iteratorパターンは、このアクセス方法を提供することで、本棚の仕様が変わったとしても、この本を取り出すところは変更する必要がないことを目的としています。

もう少し中身を見ていきます。

Bookクラス

Bookクラスは、プライベートメンバ変数として、name(本の名前)を持ちます。

また、コンストラクタはnameを引数としています。パブリックメソッドとしてはname(本の名前)を返すgetName()を持っています。

public class Book {
    private String name;

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

    public String getName() {
        return name;
    }
}

BookShelfクラス

BookShelfクラス(本棚)は、Iterableインターフェースを実装したクラスです。

Iterableインターフェースの実装は以下です。

public interface Iterable<T> {
  Iterator<T> iterator();
}

BookShelfクラス(本棚)は、プライベートメンバ変数として、Bookクラスの配列変数 books(本を格納する)を持ち、last(現在格納している本の数)を持ちます。

また、コンストラクタは、格納できる本の数 maxsize を引数としています。パブリックメソッドとしては、インデックスを指定して本を返す getBookAt() と、指定された本を本棚に追加する appendBook() と、現在格納している本の数を返す getLength() と、自分自身の Iterator を返す iterator() を持っています。

Iterableインターフェースは、Iterator を返す iterator() メソッドです。つまり、イテレータを返すメソッドを実装しなさい、ということですね。

import java.util.Iterator;

public class BookShelf implements Iterable<Book> {
    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;
    }

    @Override
    public Iterator<Book> iterator() {
        return new BookShelfIterator(this);
    }
}

BookShelfIteratorクラス

BookShelfIteratorクラス(イテレータ)は、Iteratorインタフェースを実装したクラスです。

Iteratorインタフェースの実装は以下です。

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

BookShelfIteratorクラス(イテレータ)は、プライベートメンバ変数として、BookShelfクラスのインスタンス bookShelf(本棚)を持ち、index(インデックス)を持ちます。

また、コンストラクタは、BookShelfクラス(本棚)のインスタンス bookShelf を引数としています。

パブリックメソッドは、Iteratorインタフェースを実装したメソッドとなっており、Iterator インタフェースの hasNext()(本棚に次の要素があるかどうか)と、次の要素(本)を返す next() を持っています。

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;
    }
}

Iteratorパターンのメリットと考察

Iteratorパターンを使わない場合は、BookShelfIteratorクラス(イテレータ)は作られず、Bookクラス(本)、BookShelfクラス(本棚)を作り、Mainクラスでこの2つのクラスを使って、本棚の本にアクセスすることを実現していたと思います。

具体的には、本棚の本にアクセスするのに、for文で、本棚クラスの getLength() を超えないように、getBookAt() を使って、本にアクセスするという方法で実現していたと思います(よくある実装だと思います)。

Iteratorパターンを使うことで、Mainクラスでは、この2つのメンバ関数が使われてないところがミソです。もちろん、Iteratorクラスの hasNext()next() が代わりに使われているからです。

今回は、Mainクラスだけが本棚クラスを使っているので、Iteratorパターンを使ったメリットは感じにくいのですが、たくさんの場所で本棚クラスが使われていたとする(そういう場合に多くの恩恵を受けることができる)と、本棚クラスの getLength()getBookAt() が変更されたとき、大きな影響が出てしまいます。

Iteratorパターンを使った場合は、BookShelfIteratorクラス(イテレータ)で、その変更を吸収できるので、たくさんのBookShelfクラス(本棚)を使っている場所は全く変更が必要なく対応できます。

この説明を考えるのに、数日かかってしまったというわけです(笑)

Iteratorパターンに限らないですが、変更に強い設計を考えるということは、クラスなどの部品側に変更があった場合であっても、使う側(今回の場合はMainクラス)が変更しなくていいように設計するということだと思います。

今回は以上です!

おわりに

今回はIteratorパターンを学びました。

Iteratorパターンを使うために、BookShelfIteratorクラス(イテレータ)を余計に作っていて、なぜIteratorパターンを使うと嬉しいのかを説明するのが難しかったです。

最後までお読みいただき、ありがとうございました。