GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Builder(ビルダー)
Builderパターン(ビルダー・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする。
UML class and sequence diagram
UML class diagram
■ "Builder"のサンプルプログラム
Builderパターンとは、複雑な構造のものを一気に組み立てるのは難しいので、事前に全体を構成する各部分をつくって、段階を踏んで構造を持ったインスタンスを組み上げていくものらしいです。
実際に、Builderパターンを使って、「文書」を作成するサンプルプログラムを動かしてみて、動作の様子を確認したいと思います。
-
plain
モードで、動作させると、プレーンテキスト形式の文書が出力されます。 -
html
モードで、動作させると、テーブル形式のリンク集のHTMLファイルが生成されます。
(1) plain
モードで動かしてみる
まずは、プレーンテキスト形式の文書が出力するコードを動かしてみます。
$ 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ページを作成するコードを動かしてみます。
$ python Main.py html
[Greeting.html] was created.
Greeting.html
というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Builder
- ディレクトリ構成
.
├── 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
クラスが、この役を努めます。
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
クラスが、この役を努めます。
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)
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
クラスが、この役を努めます。
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
メソッドが、この役を努めます。
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])