前書き:Builderパターンとは
Builderパターンは、GoF(Gang of Four)デザインパターンの一つであり、複雑な構造を持つインスタンス生成を容易にするための手法です。
ここでの複雑な構造とは、コンストラクタの初期化引数が多いケース(例:必須の引数2個、オプション引数5個)を考えてください。何も考えずに実装すると、「引数2個+オプション引数1個の場合のコンストラクタ」を作って、「必須引数2個+オプション引数2個の場合のコンストラクタ」を作って……と、延々と頭の悪いを実装をするハメになります。
また、コンストラクタを使用する際に、引数の順番を間違える等のコーディングミスをしてしまう可能性があります。
このようなコンストラクタの初期化引数が多いケースには、Builderパターンを適用します。Builderパターンは、リファクタリングにおける「メソッドの引き上げ(共通化している部分を別クラスに集約する)」に近く、
- InterfaceをもつBuilderクラス
- BuilderクラスのInterfaceを実装するConcreateBuilderクラス
- BuilderクラスのInterfaceでインスタンスを操作するDirectorクラス
が登場します。
必須パラメータに対してのみ設定を行うBuilderコンストラクタを呼び出し、ConcreateBuiderでオプションパラメータへの設定を行うイメージです。文字だけでは分かりづらいと思うので、本記事ではクラス図、シーケンス図、実装の順で紹介します。
Builderパターン:クラス図

Builderパターンにおいて、正式にはClientクラスは登場しません。
BuilderクラスのInterfaceであるbuild_XXX相当のメソッド数は、当然クラスの設計に合わせて増加・減少します。名称も、このクラス図に合わせる必要はありません。
Interfaceを実装しているConcreteBuilderは結果を返すメソッド(get_result)を持っており、返す内容は自由です。自身(self/this)、数値、文字列でも何でも良いです。
Builderパターン:シーケンス図

Builderパターン:Rubyによる実装例
Rubyはinterfaceが無いので、雰囲気を伝えるための実装となります。(本当はJavaで実装するのが適切ですが、Javaをササッと書けないのでRubyにしました)
以下の実装例では、オプション引数(パラメータ)として、copyright、msgを用意しました。
ConcreteBuiderクラス相当として「HTMLを生成するHtmlBuilderクラス」「Bashスクリプトを生成するBashBuilderクラス」の二つを用意しています。HtmlBuilderクラスはcopyrightを操作し、BashBuilderクラスはmsgを操作します。
Builder相当の実装
class Builder
attr_accessor :header, :body, :footer, :copyright, :msg
def initialize()
@header = ""
@body = ""
@footer = ""
@copyright = "" # オプション
@msg = "" # オプション
end
def make_header()
printf("") # interfaceメソッドのつもり
end
def make_body()
printf("") # interfaceメソッドのつもり
end
def make_footer()
printf("") # interfaceメソッドのつもり
end
end
ConcreteBuilder相当の実装
以下の実装では、オプション引数に対してbuild()メソッドで操作しています。より多くのオプションパラメータを操作する場合は、パラメータの数だけsetterメソッドを用意した方が便利です。
# HTMLBuilderクラス:ConcreteBuider相当
class HtmlBuilder < Builder
def build(copyright)
@copyright = copyright
return self
end
def make_header()
@header =<<~_EOS_
<!DOCTYPE html>
<html>
<head><title>Test Code</title></head>
_EOS_
end
def make_body()
@body = "Html Body"
end
def make_footer()
@footer = "<footer><p>" + copyright + "</p></footer>"
end
def get_result
puts(header)
puts(body)
puts(footer)
end
end
# BashBuilderクラス:ConcreteBuider相当
class BashBuilder < Builder
def build(msg)
@msg = msg
return self
end
def make_header()
@header = "#!/bin/bash"
end
def make_body()
@body = "echo " + msg
end
def make_footer()
@footer = "exit 0"
end
def get_result
puts(header)
puts(body)
puts(footer)
end
end
Director相当の実装
class Director
attr_accessor :builder
def initialize(builder)
@builder = builder
end
def construct()
builder.make_header()
builder.make_body()
builder.make_footer()
end
end
Client相当の実装
puts("HTML Builderを使用したケース")
html_dir = Director.new(HtmlBuilder.new.build('Copyright© 2020 Debimate All Rights Reserved.'))
html_dir.construct()
html_dir.builder.get_result()
puts("-----------------------------------------")
puts("Bash Builderを使用したケース")
bash_dir = Director.new(BashBuilder.new.build("Bash Body"))
bash_dir.construct()
bash_dir.builder.get_result()
[the_ad id=“598”]
Builderパターン:実装例の実行結果
HTML Builderを使用したケース
<!DOCTYPE html>
<html>
<head><title>Test Code</title></head>
<body>Html Body</body></html>
<footer>Copyright© 2020 Debimate All Rights Reserved.</footer>
-----------------------------------------
Bash Builderを使用したケース
#!/bin/bash
echo Bash Body
exit 0
他のデザインパターンに関して
GoFデザインパターン(23種類)に関しては、以下の記事でまとめてあります。
