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

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

# ■ Builder（ビルダー）
Builderパターン（ビルダー・パターン）とは、GoF（Gang of Four; 4人のギャングたち）によって定義されたデザインパターンの1つである。 オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする。
## UML class and sequence diagram
![W3sDesign_Builder_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/1d60984b-b8bf-fa00-96bc-a69e62a4c313.jpeg)
## UML class diagram
![2880px-Builder_UML_class_diagram.svg-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/f1a034f5-b5bf-6b6c-4c92-f73b7affcf1c.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://ja.wikipedia.org/wiki/Builder_パターン)


# ■ "Builder"のサンプルプログラム
Builderパターンとは、複雑な構造のものを一気に組み立てるのは難しいので、事前に全体を構成する各部分をつくって、段階を踏んで構造を持ったインスタンスを組み上げていくものらしいです。
実際に、Builderパターンを使って、「文書」を作成するサンプルプログラムを動かしてみて、動作の様子を確認したいと思います。

- `plain`モードで、動作させると、プレーンテキスト形式の文書が出力されます。
- `html`モードで、動作させると、テーブル形式のリンク集のHTMLファイルが生成されます。

## (1) `plain`モードで動かしてみる
まずは、プレーンテキスト形式の文書が出力するコードを動かしてみます。

```console
$ python Main.py plain
======================
Greeting

*** From the morning to the afternoon ***
- Good morning
- Hello
*** In the evening ***
- Good evening
- Good night
- Good bye
======================
```
所謂、プレーンテキストの文書が出力されました。

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

```console
$ python Main.py html
[Greeting.html] was created.
```
`Greeting.html`というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
![tablehtml.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/2ed4902a-8b7e-4f32-ed21-ba81f7642d48.png)

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

- ディレクトリ構成

```console
.
├── Main.py
└── builder
    ├── __init__.py
    ├── builder.py
    ├── director.py
    ├── htmlbuilder
    │   ├── __init__.py
    │   └── html_builder.py
    └── textbuilder
        ├── __init__.py
        └── text_builder.py
```

## (1) Builder（建築者）の役
`Builder`役は、インスタンスを作成するためのインタフェースを定めます。
`Builder`役には、インスタンスの各部分を作るためのメソッドが用意されます。
サンプルプログラムでは、`Builder`クラスが、この役を努めます。

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

class Builder(metaclass=ABCMeta):
    @abstractmethod
    def makeTitle(self, title):
        pass

    @abstractmethod
    def makeString(self, str):
        pass

    @abstractmethod
    def makeItems(self, items):
        pass

    @abstractmethod
    def close(self):
        pass
```

## (2) ConcreteBuilder（具体的建築者）の役
`ConcreteBuilder`役は、`Builder`役のインタフェースを実装しているクラスです。実際のインスタンス作成で呼び出されるメソッドが、ここで定義されます。また、最終的にできた結果を得るためのメソッドが用意されます。
サンプルプログラムでは、`TextBuilder`クラスや、`HTMLBuilder`クラスが、この役を努めます。

```python:builder/text_builder.py
from builder.builder import Builder

class TextBuilder(Builder):
    def __init__(self):
        self.buffer = []

    def makeTitle(self, title):
        self.buffer.append("======================\n")
        self.buffer.append(title + "\n")
        self.buffer.append("\n")

    def makeString(self, str):
        self.buffer.append("*** " + str + " ***" + "\n")

    def makeItems(self, items):
        for i in items:
            self.buffer.append("- " + i + "\n")

    def close(self):
        self.buffer.append("======================\n")

    def getResult(self):
        return ''.join(self.buffer)
```
```python:builder/html_builder.py
from builder.builder import Builder

class HTMLBuilder(Builder):
    def __init__(self):
        self.buffer = []
        self.filename = ""
        self.f = None
        self.makeTitleCalled = False

    def makeTitle(self, title):
        self.filename = title+".html"
        self.f = open(self.filename, "w")
        self.f.write("<html><head><title>"+title+"</title></head></html>")
        self.f.write("<h1>"+title+"</h1>")
        self.makeTitleCalled = True

    def makeString(self, str):
        if not self.makeTitleCalled:
            raise RuntimeError
        self.f.write("<p>"+str+"</p>")

    def makeItems(self, items):
        if not self.makeTitleCalled:
            raise RuntimeError
        self.f.write("<ul>")
        for i in items:
            self.f.write("<li>"+i+"</li>")
        self.f.write("</ul>")

    def close(self):
        if not self.makeTitleCalled:
            raise RuntimeError
        self.f.write("</body></html>")
        self.f.close()

    def getResult(self):
        return self.filename
```
## (3) Director（監督者）の役
`Director`役は、`Builder`役のインタフェースを使って、インスタンスを生成します。
`ConcreteBuilder`役に依存したプログラミングは行いません。`ConcreteBuilder`役が何であってもうまく動作するように、**`Builder`役のメソッドのみを使います**。
サンプルプログラムでは、`Director`クラスが、この役を努めます。

```python:builder/director.py
class Director(object):
    def __init__(self, builder):
        self.__builder = builder

    def construct(self):
        self.__builder.makeTitle("Greeting")
        self.__builder.makeString("From the morning to the afternoon")
        self.__builder.makeItems(["Good morning", "Hello"])
        self.__builder.makeString("In the evening")
        self.__builder.makeItems(["Good evening", "Good night", "Good bye"])
        self.__builder.close()
```
## (4) Client（依頼人）の役
`Builder`役を利用する役です。
サンプルプログラムでは、`startMain`メソッドが、この役を努めます。

```python:Main.py
import sys

from builder.director import Director
from builder.textbuilder.text_builder import TextBuilder
from builder.htmlbuilder.html_builder import HTMLBuilder

def startMain(opt):
    if opt == "plain":
        builder = TextBuilder()
        director = Director(builder)
        director.construct()
        result = builder.getResult()
        print(result)
    elif opt == "html":
        builder = HTMLBuilder()
        director = Director(builder)
        director.construct()
        result = builder.getResult()
        print("[" + result + "]" + " was created.")


if __name__ == "__main__":
    startMain(sys.argv[1])
```
# ■ 参考URL
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)
- [Builderパターン from "tachikawa844の日記"](https://tachikawa844.hatenadiary.org/entry/20090106/1231222582)