Pythonで、デザインパターン「Iterator」を学ぶ

GoFのデザインパターンを学習する素材として、書籍「[増補改訂版Java言語で学ぶデザインパターン入門](https://www.hyuki.com/dp/)」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

# ■ Iterator（イテレータ・パターン）
Iteratorパターン（イテレータ・パターン）とは、GoF（Gang of Four; 4人のギャングたち）によって定義されたデザインパターンの1つである。コンテナオブジェクトの要素を列挙する手段を独立させることによって、コンテナの内部仕様に依存しない反復子を提供することを目的とする。

## UML class and sequence diagram
![W3sDesign_Iterator_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/6ec92881-920f-3035-2d00-e8d0e4b304bd.jpeg)
## UML class diagram
![iterator.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/03ca2abe-fc03-ef69-f9f9-39027d980ef8.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://ja.wikipedia.org/wiki/Iterator_パターン)

## □ 備忘録
Iteratorパターンとは、何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのものだそうです。`Iterator`は、日本語で、**反復子**と呼ばれることもあるそうです。
Pythonプログラミングに携わっていると、よくお目にかかるやつですね。

# ■ "Iterator"のサンプルプログラム
実際に、Iteratorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

- 本棚に、`Aroun d the World in 80 days`の本を追加する
- 本棚に、`Bible`の本を追加する
- 本棚に、`Cinderella`の本を追加する
- 本棚に、`Daddy-Long-Legs`の本を追加する
- 現在、本棚に存在する本のタイトルを表示する

```console
$ 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

- ディレクトリ構成

```console
.
├── Main.py
└── iterator
    ├── __init__.py
    ├── aggregate.py
    ├── book.py
    └── iterator.py
```
## (1) Iterator（反復子）の役
要素を順番にスキャンしていくインタフェースを定める役です。
サンプルプログラムでは、`Iterator`クラスが、この役を努めます。

```python:iterator/iterator.py
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`で覚えるようになっています。

```python:iterator/book.py
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`クラスが、この役を努めます。

```python:iterator/aggregate.py
from abc import ABCMeta, abstractmethod

class Aggregate(metaclass=ABCMeta):
    @abstractmethod
    def iterator(self):
        pass
```
## (4) ConcreteAggregate（具体的な集合体）の役
`Aggregate`役が定めたインタフェースを実際に実装する役です。具体的な`Iterator`役、すなわち`ConcreteIterator`役のインスタンスを作り出します。
サンプルプログラムでは、`BookShelf`クラスが、この役を努めます。

```python:iterator/book.py
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`メソッドが、この役を努めます。

```python:Main.py
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)その他
本のタイトルを管理します。

```python:iterator/book.py
class Book(object):
    def __init__(self, name):
        self.__name = name

    def getName(self):
        return self.__name
```
## □ 備忘録 （Pythonイテレータを活用してみる！）
Pythonプログラミングで、よく、イテレータを見かける機会が多いです。
Pythonイテレータについては、こちらの[Web記事「［Python入門］イテレータとは」](https://www.atmarkit.co.jp/ait/articles/1911/12/news011.html)を参考になります。
なお、新たなクラスを定義する際に、イテレータの性質を備えるためには、次の要件を満たす必要があるそうです。

- 自分自身を戻り値とする`__iter__`メソッドを持つ
- 自分が管理している要素列の値を1つずつ返す`__next__`メソッドを持つ
- `__next__`メソッドでは、要素が尽きたら`StopIteration`例外を発生する

サンプルプログラムを**Pythonイテレータ**で書き換えてみたいと思います。
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Iterator/step2

```python:iterator/book.py
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
```
```python:Main.py
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()
```
だいぶ、シンプルになりました。
では、動かしてみます。

```console
$ python Main.py 
Aroun d the World in 80 days
Bible
Cinderella
Daddy-Long-Legs
```
最初の動作結果と同じになりましたので、これで、ひとまず、完成とします。

# ■ 参考URL
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)
- [Web記事「［Python入門］イテレータとは」](https://www.atmarkit.co.jp/ait/articles/1911/12/news011.html)