GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Iterator(イテレータ・パターン)
Iteratorパターン(イテレータ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。コンテナオブジェクトの要素を列挙する手段を独立させることによって、コンテナの内部仕様に依存しない反復子を提供することを目的とする。
UML class and sequence diagram
UML class diagram
□ 備忘録
Iteratorパターンとは、何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのものだそうです。Iterator
は、日本語で、反復子と呼ばれることもあるそうです。
Pythonプログラミングに携わっていると、よくお目にかかるやつですね。
■ "Iterator"のサンプルプログラム
実際に、Iteratorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 本棚に、
Aroun d the World in 80 days
の本を追加する - 本棚に、
Bible
の本を追加する - 本棚に、
Cinderella
の本を追加する - 本棚に、
Daddy-Long-Legs
の本を追加する - 現在、本棚に存在する本のタイトルを表示する
$ python Main.py
Aroun d the World in 80 days
Bible
Cinderella
Daddy-Long-Legs
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Iterator/step1
- ディレクトリ構成
.
├── Main.py
└── iterator
├── __init__.py
├── aggregate.py
├── book.py
└── iterator.py
(1) Iterator(反復子)の役
要素を順番にスキャンしていくインタフェースを定める役です。
サンプルプログラムでは、Iterator
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
class Iterator(metaclass=ABCMeta):
@abstractmethod
def hasNext(self):
pass
@abstractmethod
def next(self):
pass
(2) ConcreteIterator(具体的な反復子)の役
Iterator
役で定めたインタフェースを実際に実装する役です。
サンプルプログラムでは、BookShelfIterator
クラスが、この役を努めます。
この役は、スキャンするために必要な情報を持っている必要があります。
サンプルプログラムでは、BookShelf
クラスのインスタンスをインスタンス変数self.__bookShelf
で覚えており、注目している本をインスタンス変数self.__index
で覚えるようになっています。
from iterator.iterator import Iterator
...(snip)
class BookShelfIterator(Iterator):
def __init__(self, bookShelf):
self.__bookShelf = bookShelf
self.__index = 0
def hasNext(self):
return True if self.__index < self.__bookShelf.getLength() else False
def next(self):
book = self.__bookShelf.getBookAt(self.__index)
self.__index += 1
return book
(3) Aggregate(集合体)の役
Iterator
役を作り出すインタフェースを定める役です。そのインタフェースは、「私が持っている要素を順番にスキャンしてくれる人」を作り出す抽象化メソッドということになります。
サンプルプログラムでは、Aggregate
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
class Aggregate(metaclass=ABCMeta):
@abstractmethod
def iterator(self):
pass
(4) ConcreteAggregate(具体的な集合体)の役
Aggregate
役が定めたインタフェースを実際に実装する役です。具体的なIterator
役、すなわちConcreteIterator
役のインスタンスを作り出します。
サンプルプログラムでは、BookShelf
クラスが、この役を努めます。
from iterator.aggregate import Aggregate
...(snip)
class BookShelf(Aggregate):
def __init__(self, maxSize):
self.__last = 0
self.__books = [None] * maxSize
def getBookAt(self, index):
return self.__books[index]
def append(self, book):
self.__books[self.__last] = book
self.__last += 1
def getLength(self):
return self.__last
def iterator(self):
return BookShelfIterator(self)
(5) Client(依頼人)の役
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
from iterator.book import Book, BookShelf
def startMain():
bookShelf = BookShelf(4)
bookShelf.append(Book(name="Aroun d the World in 80 days"))
bookShelf.append(Book(name="Bible"))
bookShelf.append(Book(name="Cinderella"))
bookShelf.append(Book(name="Daddy-Long-Legs"))
it = bookShelf.iterator()
while it.hasNext():
book = it.next()
print(book.getName())
if __name__ == '__main__':
startMain()
(6)その他
本のタイトルを管理します。
class Book(object):
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
□ 備忘録 (Pythonイテレータを活用してみる!)
Pythonプログラミングで、よく、イテレータを見かける機会が多いです。
Pythonイテレータについては、こちらのWeb記事「[Python入門]イテレータとは」を参考になります。
なお、新たなクラスを定義する際に、イテレータの性質を備えるためには、次の要件を満たす必要があるそうです。
- 自分自身を戻り値とする
__iter__
メソッドを持つ - 自分が管理している要素列の値を1つずつ返す
__next__
メソッドを持つ -
__next__
メソッドでは、要素が尽きたらStopIteration
例外を発生する
サンプルプログラムをPythonイテレータで書き換えてみたいと思います。
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Iterator/step2
class Book(object):
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
class BookShelf(object):
def __init__(self):
self.__books = []
def append(self, book):
self.__books.append(book)
def __iter__(self):
self.__index = 0
return self
def __next__(self):
if self.__index >= len(self.__books):
raise StopIteration()
book = self.__books[self.__index]
self.__index += 1
return book
from iterator.book import Book, BookShelf
def startMain():
bookShelf = BookShelf()
bookShelf.append(Book(name="Aroun d the World in 80 days"))
bookShelf.append(Book(name="Bible"))
bookShelf.append(Book(name="Cinderella"))
bookShelf.append(Book(name="Daddy-Long-Legs"))
for book in bookShelf:
print(book.getName())
if __name__ == '__main__':
startMain()
だいぶ、シンプルになりました。
では、動かしてみます。
$ python Main.py
Aroun d the World in 80 days
Bible
Cinderella
Daddy-Long-Legs
最初の動作結果と同じになりましたので、これで、ひとまず、完成とします。