Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Builder
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Builder パターンの使われる場面◆◆

要は,複合オブジェクトから成り立つモジュールの利用者であるクライアントから見たときに非複合オブジェクトに見えるカプセル化(隠蔽)の方法である。

たくさんのオブジェクトから成り立つ一連の多様な作業を標準のアルゴリズムで実装し,個々の作業内容は別の独立したクラスに任すことができる。


(2023-11-18)Python3.11で動作確認済み

【重要な注意】本ソースコードファイルを起動するには第2引数が必要です。ターミナルからPythonを起動するとき,普通はプロンプト「'>'」の後に次のようにタイプします。

>python Builder.py plain(またはhtml)

ターミナルによっては'python'は'python3'になります。ソースコードファイル(スクリプトファイル)名称が第1引数です。第2引数は本プログラムに読み込むオプションです。

「PyCharm」では,次の枠に第2引数を設定します。
  メニュー→run→Edit Configurations...→左下矢印と右上矢印の枠
  ファイル名→Modify Run Configuration...→左下矢印と右上矢印の枠
  画面によっては,Interpreter options: という枠です(左下矢印と右上矢印の枠)

「Jupyter Notebook」では,自己テストの1行目に「sys.argv[1]=」を入れます。「PyCharm」では,この行はありません。「Jupyter Notebook」にこの行を入れるのは,ハードコードと言い,固有名詞や固有な数値をコマンドラインに埋め込むことは本来避けるべきですが,この場合はやむを得ないとしましょう。ちなみにこのコードでは第1引数のような扱いになっていますが,ターミナルのコマンドラインでは第2引数なので混乱しないように。

「sys.argv[1]=」の前かまたは冒頭に「import sys」を忘れないように。

.pyではターミナルから実行されたとき自己テストが実行され,他のファイルから呼ばれたときは自己テストは無視されることも忘れないように。「Jupyter Notebook」では呼ばれる側を上側に置き,下側に参照されるようにします(本記事に関係ないかも)。


◆◆Builder パターンとは◆◆

GoFによれば,Builder パターンの目的は, 「複合オブジェクトについて,その作成過程を表現形式に依存しないものにすることにより,同じ作成過程で異なる表現形式のオブジェクトを生成できるようにする。」

GoFによれば,Builder パターンは次のような場合に適用する。
・多くの構成要素からなるオブジェクトを生成するアルゴリズムを,構成要素自体やそれらがどのように組み合わされるのかということから独立にしておきたい場合。
・オブジェクトの作成プロセスが,オブジェクトに対する多様な表現を認めるようにしておかなければならない場合。

GoFによれば,関連するパターンは次のようなものである。
Abstract Factory パターン: このパターンは,複合オブジェクトを作成するという点で Builder パターンに類似している。主な違いは,Builder パターンでは複合オブジェクトを段階的に作成していく過程に焦点をあてているのに対して,Abstract Factory パターンでは部品の集合を強調している(それが単純であっても複雑であっても)という点である。Builder パターンでは,Product オブジェクトを最終段階で返すことになるが,Abstract Factory パターンでは即座に返す。 Composite パターン: builder は,しばしば composite を作成する。

◆◆Builder パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。
                   Builder                     ←  Director
    (makeTitle, makeString, makeItems, close)     (construct)
           ↑                ↑
       TextBuilder       HTMLBuilder
    (do., getResult)    (do., getResult)

サンプルは,普通のテキストとHTML コードを同じ手続きでつくります。

class Builder は,テンプレートであり,抽象クラスです。
class TextBuilder は,普通のテキストを扱う具象クラスです。
class HTMLBuilder は,HTML コードを扱う具象クラスです。
class Director は,監督の役目です。class Builder 作業者に指示を与えます。

◆◆Builder パターンの実装◆◆

サンプルは,普通のテキストとHTML コードを同じ手続きでつくるものです。その切り替えは,起動時のコマンドラインの引数によります。その書き方はメインルーチンの引数がないときの警告文に書いてあります。

コマンドライン引数の読み込みは,メインルーチンの
  sys.argv[1] == "plain"
  sys.argv[1] == "html"
によりできます。引数の数え方は Java とは違い,[0] はこのファイル名になります。

「class Builder」は作業者であり「class Director」は監督の役目です。「class Builder」はJavaでは抽象クラスと抽象メソッドであり,Pythonでもメソッドを宣言しているだけで何もしていません。個々の作業者となるサブクラスとして「class TextBuilder」と「HTMLBuilder」があり個々の作業をするようになっています。さらに別の作業が必要なときはこのサブクラスを交換すればよいことになります。

メインルーチンで切り替えがされた後,メインルーチンで,「class Builder」のサブクラス(個々の作業者)をインスタンス化してそれを引数にして「class Director」をインスタンス化しています。「class Director」からサブクラス(個々の作業者)のメソッドを呼ぶときはもらった引数(サブクラスのインスタンス)を前に付けています。このようにすれば if 文を使わないでサブクラスを呼び分けることができます。ここがオブジェクト指向の重要なポイントです。

「class Director」のメソッドでは共通の文を用意していて,個々の作業者である「class TextBuilder」はその文を標準出力に書き出し,別の作業者である「class HTMLBuilder」はHTMLコードをhtmlファイルに書き出します。htmlファイルをブラウザで見れば標準出力の文と同じに見えるのです(巻末に掲示してある)。

つまり,「class Director」のメソッドは標準の部品を用意していて,それの処理/加工は個々の作業者に対して同じ名称で指示する仕組みになっているのです。このたくさんの一連のオブジェクトは標準化されていて,個々の作業者であるサブクラスのメソッドを隠蔽しているのでカプセル化と言えます。ここもオブジェクト指向の重要なポイントです。

◆◆ソースコード◆◆

このWebの左上隅からダウンロードできます。

ソースファイルは1つです。サンプル出力も添付しました。
・Builder.py;Builder パターンのPythonサンプル
・Greeting.html;サンプル出力の html ファイル

【Builder.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from abc import ABCMeta, abstractmethod

class Builder(metaclass = ABCMeta):
    #__metaclass__ = ABCMeta
    @abstractmethod
    def makeTitle(self, title):
        pass
    @abstractmethod
    def makeString(self, st):
        pass
    @abstractmethod
    def makeItems(self, items):
        pass
    @abstractmethod
    def _close(self):
        pass

class Director(object):
    def __init__(self, builder):
        self.builder = builder
    def construct(self):
        self.builder.makeTitle("Greeting")
        self.builder.makeString("朝から昼にかけて")
        self.builder.makeItems(
            ["おはようございます。", "こんにちは。"])
        self.builder.makeString("夜に")
        self.builder.makeItems(
            ["こんばんは。", "おやすみなさい。", "さようなら。"])
        self.builder._close()

class TextBuilder(Builder):
    _buffer = []
    def makeTitle(self, title):
        self._buffer.append("===================================\n")
        self._buffer.append("『{0}』".format(title))
        #self._buffer.append("\n")
    def makeString(self, st):
        self._buffer.append("■{0}\n".format(st))
        #self._buffer.append("\n")
    def makeItems(self, items):
        for i in items:
            self._buffer.append(" ・{0}\n".format(i))
        #self._buffer.append("\n")
    def _close(self):
        self._buffer.append("===================================\n")
    def getResult(self):
        return "\n".join(self._buffer)

class HTMLBuilder(Builder):
    _buffer = []
    def makeTitle(self, title):
        self.title = title
        self.filename = "{0}.html".format(title)
        self._buffer.append(
            "<html><head><title>{0}</title></head><body>\n".format(title))
        self._buffer.append("<h1>{0}</h1>\n".format(title))
    def makeString(self, st):
        self.st = st
        self._buffer.append("<p>{0}</p>\n".format(st))
    def makeItems(self, items):
        self.items = items
        self._buffer.append("<ul>\n")
        for i in items:
            self._buffer.append("<li>{0}</li>\n".format(i))
        self._buffer.append("</ul>\n")
    def _close(self):
        self._buffer.append("</body></html>\n")
    def getResult(self):
        self.writer = open(self.filename, "w")
        self.writer.write("\n".join(self._buffer))
        self.writer.close()
        return self.filename

def main():
    if len(sys.argv) == 1:
        usage()
    elif sys.argv[1] == "plain":
        textbuilder = TextBuilder()
        director = Director(textbuilder)
        director.construct()
        result = textbuilder.getResult()
        sys.stdout.write(result)
    elif sys.argv[1] == "html":
        htmlbuilder = HTMLBuilder()
        director = Director(htmlbuilder)
        director.construct()
        filename = htmlbuilder.getResult()
        sys.stdout.write("{0}が作成されました。".format(filename))
    else:
        usage()

def usage():
    sys.stdout.write(
        "Usage:python Builder.py plain;プレーンテキストで文書作成\n")
    sys.stdout.write(
        "Usage:python Builder.py html;HTMLファイルで文書作成\n")

if __name__=='__main__':
    main()
"""標準出力
コマンドライン : plain
===================================

『Greeting』
■朝から昼にかけて

 ・おはようございます。

 ・こんにちは。

■夜に

 ・こんばんは。

 ・おやすみなさい。

 ・さようなら。

===================================
"""

【出力されたHTMLコード】
(あなたは出力されたコードそのものではなくブラウザを通して見てるはず)

Greeting

Greeting

朝から昼にかけて

夜に

inserted by FC2 system 以上

トップページに戻る
inserted by FC2 system