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だとコンパイルエラーにならないんですね。。。
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みたいに変数に型をつけられなかったり、循環インポートの罠があったりで大変ですね。
実際どう便利か?
上記のソースコードの例を見てわかるとおり、入れ物と入れ物を数え上げる仕組みが別になっております。
つまり入れ物の中を変更せずとも、数え上げる仕組みに何かを追加することで数え上げにアレンジを加えることができます。
数え上げのための共通の仕組みがあることで、データベースからデータ一覧を抽出した時のデータの形式が共通だったりするので、設計の上ではとても大事なデザインパターンです。
この仕組みが考慮されていないと、ライブラリやフレームワークによって出力したデータのいじり方が変わってきたりしてとても大変でしょうね。