【Builderパターン】コンストラクタの初期化(new)引数が多い場合にオススメなデザインパターン
前書き: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相当の実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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メソッドを用意した方が便利です。
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 |
# 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相当の実装
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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相当の実装
1 2 3 4 5 6 7 8 9 10 |
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() |
Builderパターン:実装例の実行結果
1 2 3 4 5 6 7 8 9 10 11 |
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種類)に関しては、以下の記事でまとめてあります。
【オブジェクト指向】全23種類のGoFデザインパターンに関する説明と参考書籍
ロシア人と国際結婚した地方エンジニア。
小学〜大学院、就職の全てが新潟。
大学の専攻は福祉工学だったのに、エンジニアとして就職。新卒入社した会社ではOS開発や半導体露光装置ソフトを開発。現在はサーバーサイドエンジニアとして修行中。HR/HM(メタル)とロシア妻が好き。サイトに関するお問い合わせやTwitterフォローは、お気軽にどうぞ。
1件の返信
[…] Builder […]