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

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

# ■ Abstract Factory（アブストラクトファクトリ）
Abstract Factory パターン（アブストラクト・ファクトリ・パターン）とは、GoF（Gang of Four; 4人のギャングたち）によって定義されたデザインパターンの1つである。 関連するインスタンス群を生成するための API を集約することによって、複数のモジュール群の再利用を効率化することを目的とする。日本語では「抽象的な工場」と翻訳される事が多い。Kit パターンとも呼ばれる

## UML class and sequence diagram
![W3sDesign_Abstract_Factory_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/c7a5008a-06ae-2fbc-5e17-5639b7073e38.jpeg)
## UML class diagram
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3130333539352f62393731323034392d616664392d323330332d653539372d3031393136333530633732382e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/944b8a7b-5d2a-093f-bbdb-15b06d6e4e38.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://ja.wikipedia.org/wiki/Abstract_Factory_パターン)


# ■ "Abstract Factory"のサンプルプログラム
Abstract Factoryパターンとは、**抽象的な工場**から、抽象的な部品を組み合わせて抽象的な製品を作るらしいです。
実際に、Abstract FactoryのPython実装コードを動かしてみて、**抽象的な工場**をイメージを理解したいと思います。ここで取り上げるサンプルプログラムは、階層構造を持ったリンク集をHTMLファイルとして作るものです。

- `ListFactory`モードで、動作させると、リスト形式のリンク集のHTMLファイルが生成されます。
- `TableFactory`モードで、動作させると、テーブル形式のリンク集のHTMLファイルが生成されます。




## (1) ListFactoryを動かしてみる
まずは、**Linkベース**のWebページを作成するコードを動かしてみます。

```console
$ python Main.py ListFactory
[LinkPage.html] was created.
```
`LinkPage.html`というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
![listfactory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/13ba9a4a-c09e-4815-6c6a-fb2a5d31ff9c.png)

## (2) TableFactoryを動かしてみる
つづいて、**Tableベース**のWebページを作成するコードを動かしてみます。

```console
$ python Main.py TableFactory
[LinkPage.html] was created.
```
`LinkPage.html`というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
![tablefactory.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/43c45b11-1941-f605-03c3-dcd27e1f99ad.png)


# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/AbstractFactory

- ディレクトリ構成

```console
.
├── Main.py
└── factory
    ├── __init__.py
    ├── factory.py
    ├── listfactory
    │   ├── __init__.py
    │   └── list_factory.py
    └── tablefactory
        ├── __init__.py
        └── table_factory.py
```

## (1) AbstractProduct（抽象的な製品）の役
`AbstractProduct`役は、`AbstractFactory`役によって、作り出される抽象的な部品や製品のインタフェースを定めます。サンプルプログラムでは、`Link`クラス、`Tray`クラス、`Page`クラスが、この役を努めます。

```python:factory/factory.py
import sys
from abc import ABCMeta, abstractmethod

... (snip)

class Item(metaclass=ABCMeta):
    def __init__(self, caption):
        self.caption = caption

    @abstractmethod
    def makeHtml(self):
        pass

class Link(Item, metaclass=ABCMeta):
    def __init__(self, caption, url):
        super().__init__(caption)
        self.url = url

class Tray(Item, metaclass=ABCMeta):
    def __init__(self, caption):
        super().__init__(caption)
        self.tray = []

    def add(self, item):
        self.tray.append(item)

class Page(metaclass=ABCMeta):
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.content = []

    def add(self, item):
        self.content.append(item)

    def output(self):
        try:
            filename = self.title + '.html'
            writer = open(filename, 'w')
            writer.write(self.makeHtml())
            writer.close()
            print("[" + filename + "]" + " was created.")
        except Exception as e:
            print(e)
            sys.exit(1)

    @abstractmethod
    def makeHtml(self):
        pass
```
## (2) AbstractFactory（抽象的な工場）の役
`AbstractFactory`役は、`AbstractProduct`役のインスタンスを作り出すためのインタフェースを定めます。
サンプルプログラムでは、`Factory`クラスがこの役を勤めます。

```python:factory/factory.py
import sys
from abc import ABCMeta, abstractmethod

class Factory(metaclass=ABCMeta):
    @abstractmethod
    def createLink(self, caption, url):
        pass

    @abstractmethod
    def createTray(self, caption):
        pass

    @abstractmethod
    def createPage(self, title, author):
        pass

... (snip)
```

## (3) Client（依頼者）の役
`Client`役は、`AbstractFactory`役と`AbstractProduct`役のインタフェースだけを使って仕事を行います。
`Client`役は、具体的な部品や製品や工場については、知りません。なお、サンプルプログラムでは、`startMain `メソッドが、この役を努めます。

```python:Main.py
import sys
import inspect
import factory

def startMain(factoryObject):
    asahi = factoryObject.createLink("Asahi", "http://www.asahi.com")
    yomiuri = factoryObject.createLink("Yomiuri", "http://www.yomiuri.co.jp")
    us_yahoo = factoryObject.createLink("Yahoo", "http://www.yahoo.com")
    jp_yahoo = factoryObject.createLink("Yahoo!Japan", "http://www.yahoo.co.jp")
    google = factoryObject.createLink("Google", "http://www.google.com")
    excite = factoryObject.createLink("Excite", "http://www.excite.co.jp")

    traynews = factoryObject.createTray("Newspaper")
    traynews.add(asahi)
    traynews.add(yomiuri)

    trayyahoo = factoryObject.createTray("Yahoo!")
    trayyahoo.add(us_yahoo)
    trayyahoo.add(jp_yahoo)

    traysearch = factoryObject.createTray("Search Engine")
    traysearch.add(trayyahoo)
    traysearch.add(excite)
    traysearch.add(google)

    page = factoryObject.createPage("LinkPage", "Hiroshi Yuki")
    page.add(traynews)
    page.add(traysearch)
    page.output()

if __name__ == '__main__':
    for _, plugin in inspect.getmembers(factory, inspect.isclass):
        if plugin.__name__ == sys.argv[1]:
            startMain(plugin())
```
## (4) ConcreteProduct（具体的な製品）の役
`ConcreteProduct`役は、`AbstractProduct`役のインタフェースを実装します。サンプルプログラムでは、以下のクラスが、この役を努めます。

- `ListLink`クラス、`ListTray`クラス、`ListPage`クラス
- `TableLink`クラス、`TableTray`クラス、`TablePage`クラス

```python:factory/listfactory/list_factory.py
from factory.factory import Factory, Link, Tray, Page

... (snip)

class ListLink(Link):
    def __init__(self, caption, url):
        super().__init__(caption, url)

    def makeHtml(self):
        return '  <li><a href="{}">{}</a></li>\n'.format(self.url, self.caption)

class ListTray(Tray):
    def __init__(self, caption):
        super().__init__(caption)

    def makeHtml(self):
        buf = []
        buf.append('<li>\n')
        buf.append(self.caption + '\n')
        buf.append('<ul>\n')

        for item in self.tray:
            buf.append(item.makeHtml())

        buf.append('</ul>\n')
        buf.append('</li>\n')
        return ''.join(buf)

class ListPage(Page):
    def __init__(self, title, author):
        super().__init__(title, author)

    def makeHtml(self):
        buf = []
        buf.append('''
<html>
  <head><title>{}</title></head>
'''.format(self.title))
        buf.append('<body>\n')
        buf.append('<h1>{}</h1>'.format(self.title))
        buf.append('<ul>')

        for item in self.content:
            buf.append(item.makeHtml())

        buf.append('</ul>')
        buf.append('<hr><adress>{}</adress>'.format(self.author))
        buf.append('</body>\n</html>\n')
        return ''.join(buf)
```
```python:factory/tablefactory/table_factory.py
from factory.factory import Factory, Link, Tray, Page

... (snip)

class TableLink(Link):
    def __init__(self, caption, url):
        super().__init__(caption, url)

    def makeHtml(self):
        return '<td><a href={}>{}</a></td>'.format(self.url, self.caption)

class TableTray(Tray):
    def __init__(self, caption):
        super().__init__(caption)

    def makeHtml(self):
        buf = []
        buf.append('<td>')
        buf.append('<table width="100%" border="1"><tr>')
        buf.append('<td bgcolor="#cccccc" algin="center" colsapn="{}"><b>{}</b></td>'.format(len(self.tray), self.caption))
        buf.append('</tr>\n')
        buf.append('<tr>\n')

        for item in self.tray:
            buf.append(item.makeHtml())

        buf.append('</tr></table>')
        buf.append('</td>')
        return ''.join(buf)

class TablePage(Page):
    def __init__(self, title, author):
        super().__init__(title, author)

    def makeHtml(self):
        buf = []
        buf.append('''
<html>
  <head><title>{}</title></head>
    '''.format(self.title))
        buf.append('<body>\n')
        buf.append('<h1>{}</h1>'.format(self.title))
        buf.append('<table width="80%" border="3">\n')

        for item in self.content:
            buf.append('<tr>{}</tr>'.format(item.makeHtml()))

        buf.append('</table>')
        buf.append('<hr><adress>{}</adress>'.format(self.author))
        buf.append('</body>\n</html>\n')
        return ''.join(buf)
```

## (5) ConcreteFactory（具体的な工場）の役
`ConcreteFactory`役は、`AbstractFactory`役のインタフェースを実装します。サンプルプログラムでは、以下のクラスが、この役を努めます。

- `ListFactory`クラス
- `TableFactory`クラス

```python:factory/__init__.py
from factory.listfactory.list_factory import ListFactory
from factory.tablefactory.table_factory import TableFactory

__all__ = [
    "ListFactory",
    "TableFactory"   
]
```

```python:factory/listfactory/list_factory.py
from factory.factory import Factory, Link, Tray, Page

class ListFactory(Factory):
    def createLink(self, caption, url):
        return ListLink(caption, url)

    def createTray(self, caption):
        return ListTray(caption)

    def createPage(self, title, author):
        return ListPage(title, author)

... (snip)
```

```python:factory/tablefactory/table_factory.py
from factory.factory import Factory, Link, Tray, Page

class TableFactory(Factory):
    def createLink(self, caption, url):
        return TableLink(caption, url)

    def createTray(self, caption):
        return TableTray(caption)

    def createPage(self, title, author):
        return TablePage(title, author)

... (snip)
```


# ■ 参考URL
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)
- [Abstract Factoryパターン from "tachikawa844の日記"](https://tachikawa844.hatenadiary.org/entry/20090110/1231561119)