さるへい備忘録

さるへいのやったことを綴っているブログです。基本的にテクノロジーの話題です。

Pythonで学ぶデザインパターン入門 Iteratorパターン

結城先生の Java言語で学ぶデザインパターン入門 のコードをPythonに書き直していくシリーズ第1段です。

Iteratorパターンとは

ざっくり言えばデータを順番にひとつひとつ数え上げていくパターンです。
具体的には、入れ物(Aggregate)に対して、反復して数える仕組み(Iterator)を構築すると行った仕組みです。
そんなの普通じゃんと言われればそれまでですが、ひとまず紹介させていただきます。

ソースコード

コードの概要

ここでは、入れ物は本棚(BookShelf)で数え上げる仕組みはBookShelfIterator と定義されます。 本棚の中に本を入れていき、最後にはすべてを順番にひとつずつ読み上げていくコードです。

具体的なソースコード

Aggragete(入れ物)

# coding: utf-8
from abc import ABCMeta, abstractmethod

from Iterator.iterator import Iterator


class Aggregate(metaclass=ABCMeta):

    @abstractmethod
    def iterator(self) -> Iterator:
        pass

こちらは入れ物の抽象クラスになります。
具体的な本棚などの入れ物は、こちらを継承します。

Iterator(数え上げる仕組み)

# coding: utf-8
from abc import ABCMeta, abstractmethod


class Iterator(metaclass=ABCMeta):

    @abstractmethod
    def has_next(self) -> bool:
        pass

    @abstractmethod
    def next(self):
        pass

こちらは数え上げる仕組みの抽象クラスになります。
本棚を数え上げる仕組みなどの具体的な数え上げる仕組みはこちらを継承します。

Book(本)

# coding: utf-8


class Book(object):

    def __init__(self, name: str):
        self.__name = name

    def get_name(self) -> str:
        return self.__name

こちらは実際に数え上げるものになります。

BookShelf(本棚)

# coding: utf-8
from Iterator.Aggregate import Aggregate
from Iterator.book import Book
from Iterator.iterator import Iterator


class BookShelf(Aggregate):

    def __init__(self, maxsize: int):
        self.__book = [Book('undefined')] * maxsize
        self.__last = 0

    def get_book_at(self, index: int) -> Book:
        return self.__book[index]

    def append_book(self, book: Book):
        self.__book[self.__last] = book
        self.__last += 1

    def get_length(self) -> int:
        return self.__last

    def iterator(self) -> Iterator:
        from Iterator.book_shelf_iterator import BookShelfIterator
        return BookShelfIterator(self)

こちらは数え上げるものを入れるために入れ物ですね。
Aggregateが継承されています。 写経のままだとどうしても循環インポートになっちゃうので、メソッドローカルに無理やりインポートしました。 Javaだとコンパイルエラーにならないんですね。。。

vermeer.hatenablog.jp

Pythonだとどれが最適解なのか、PHPみたいにnamespaceじみたことをするか、 BookShelfIterator を同じファイルに書くかとかしか思いつかないですね。。。

BookShelfIterator(実際に本棚をひとつひとつ数えあげる仕組み)

# coding: utf-8
from Iterator.book_shelf import BookShelf
from Iterator.iterator import Iterator


class BookShelfIterator(Iterator):

    def __init__(self, bookshelf: BookShelf):
        self.__bookshelf = bookshelf
        self.__index = 0

    def has_next(self) -> bool:
        if self.__index < self.__bookshelf.get_length():
            return True
        else:
            return False

    def next(self):
        book = self.__bookshelf.get_book_at(self.__index)
        self.__index += 1
        return book

こちら、本棚の中身をひとつひとつ数え上げるための仕組みですね。
Iteratorが継承されています。

実行ファイル

# coding: utf-8
from Iterator.book import Book
from Iterator.book_shelf import BookShelf

if __name__ == '__main__':
    bookshelf = BookShelf(4)
    bookshelf.append_book(Book('Around the World in 80 Days'))
    bookshelf.append_book(Book('Bible'))
    bookshelf.append_book(Book('Cinderella'))
    bookshelf.append_book(Book('Daddy-Long-Legs'))
    it = bookshelf.iterator()
    while it.has_next():
        book = it.next()
        print(book.get_name())

実際にPythonに書いてみると、Javaみたいに変数に型をつけられなかったり、循環インポートの罠があったりで大変ですね。

実際どう便利か?

上記のソースコードの例を見てわかるとおり、入れ物と入れ物を数え上げる仕組みが別になっております。
つまり入れ物の中を変更せずとも、数え上げる仕組みに何かを追加することで数え上げにアレンジを加えることができます。

数え上げのための共通の仕組みがあることで、データベースからデータ一覧を抽出した時のデータの形式が共通だったりするので、設計の上ではとても大事なデザインパターンです。
この仕組みが考慮されていないと、ライブラリやフレームワークによって出力したデータのいじり方が変わってきたりしてとても大変でしょうね。