Builderパターン

概要

一言で言うと、「同じ生成過程で素材の異なるオブジェクトを作る」パターンです。

例題

①タイトル
②ヘッダー
③本文
④フッター

が必要な文章を、HTMLとPlainTextで作成します。

サンプルコード

import abc


def main():
    html = Director().construct(HTMLBuilder())
    text = Director().construct(TextBuilder())
    print(html)
    print("****************")
    print(text)


class Director():

    def construct(self, builder):
        all_str = ""
        all_str += builder.build_title("Monthly Report")
        all_str += builder.build_header("-------")
        all_str += builder.build_contents(["Monday: 20", "Tuesday: 30"])
        all_str += builder.build_footer("-*-*-*-")
        return all_str


class AbstractBuilder(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def build_title(self, title):
        pass

    @abc.abstractmethod
    def build_header(self, header):
        pass

    @abc.abstractmethod
    def build_contents(self, contents):
        pass

    @abc.abstractmethod
    def build_footer(self, footer):
        pass


# ConcreteBuilder
class HTMLBuilder(AbstractBuilder):

    def build_title(self, title):
        return "<h1>{}</h1>\n".format(title)

    def build_header(self, header):
        return "<header><p>{}</p></header>\n".format(header)

    def build_contents(self, contents):
        html_contents = []
        for content in contents:
            html_contents.append("<p>{}</p>\n".format(content))
        return "".join(html_contents)

    def build_footer(self, footer):
        return "<footer><p>{}</p></footer>\n".format(footer)


# ConcreteBuilder
class TextBuilder(AbstractBuilder):

    def build_title(self, title):
        return "**{}**\n".format(title)

    def build_header(self, header):
        return "{}\n".format(header)

    def build_contents(self, contents):
        text_contents = []
        for content in contents:
            text_contents.append("{}\n".format(content))
        return "".join(text_contents)

    def build_footer(self, footer):
        return "{}\n".format(footer)


if __name__ == "__main__":
    main()

# 出力

<h1>Monthly Report</h1> <header><p>-------</p></header> <p>Monday: 20</p> <p>Tuesday: 30</p> <footer><p>-*-*-*-</p></footer> **************** \**Monthly Report** ------- Monday: 20 Tuesday: 30 -*-*-*-

特徴

大事なのはDicectorクラスを噛ませていることです。

class Director():
    def construct(self, builder):
        all_str = ""
        all_str += builder.build_title("Monthly Report")
        all_str += builder.build_header("-------")
        all_str += builder.build_contents(["Monday: 20", "Tuesday: 30"])
        all_str += builder.build_footer("-*-*-*-")
        return all_str

constructメソッドにに具体的な手順を書き込み、実際の作成はbuilderに任せることで、 オプションがかなり簡易化されます。

引数をbuilderにしないと、以下のような「telescoping constructor」アンチパターンとなってしまいます。

class Director():
    def construct(self, format="html", title="", header="", contents=[], footer=""):
        # ...

Pythonらしい書き方

特になし。

サンプルコードではabcクラスを使用しています。

クラス図

# 一般的なクラス図

一般的なクラス図(Builderパターン)

# サンプルコードにおけるクラス図

サンプルコードにおけるクラス図(Builderパターン)

参考

Wikipedia
クラス図をお借りしました。

Tech Scoreさま
概念的なところの説明に一部参考にさせていただきました。