#はじめに
本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「[Java言語で学ぶデザインパターン入門] (http://www.hyuki.com/dp/)」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。
第1回:[Iteratorパターン] (http://qiita.com/Nuits/items/800f080d44b52e1cae18)
第2回:[Adapterパターン] (http://qiita.com/Nanakusajp/items/747d1913b1e8bea8d340)
第3回:[Template Methodパターン] (http://qiita.com/_kyam/items/9b5722e6a9635af6e18b)
第4回:[Factory Methodパターン] (http://qiita.com/ayayo/items/3eece04bbc4b7504179a)
第5回:[Singletonパターン] (http://qiita.com/Keenag/items/cae53d55cb953562fee4)
第6回:[Prototypeパターン] (http://qiita.com/aconit96/items/9235d30a07f508886c7e)
#Builderパターンとは
Builderパターンとは、構造を持ったオブジェクトの生成過程を抽象化することで、異なる形式のオブジェクトを表現するデザインパターンです。
ここでは「文書」を作成するプログラムを例に紹介します。
#サンプルプログラム
##クラス図
##各クラスの役割
名前 | 役割 |
---|---|
Builder | 文書を構成するためのメソッドを定めたインターフェース |
Director | ひとつの文書を作成するクラス |
TextBuilder | プレーンテキストを使って文書を作成するクラス |
HTMLBuilder | HTMLファイルを使って文書を作成するクラス |
Program | 動作確認用クラス |
##Builderインターフェース
Builderインターフェースには「文書」の構造を作成するために必要なメソッドが定義されています。
それぞれ、タイトルを印字するmakeTitleメソッド、文字列を印字するmakeStringメソッド、箇条書き文字列を印字するmakeItemsメソッドとなっており、closeメソッドで文書を完成させます。
namespace DesignPattern_Builder
{
public interface Builder
{
void makeTitle(string title);
void makeString(string str);
void makeItems(String[] items);
void close();
}
}
##Directorクラス
DirectorクラスはBuilderインターフェースで定義されているメソッドを利用して文書を作成します。
コンストラクタで与えられるBuilderの実装クラスのインスタンスにより作成する文書の形式が定まります。
constructメソッドによって、文書が構成されます。
public class Director
{
private readonly Builder builder;
public Director(Builder builder)
{
this.builder = builder;
}
public void construct()
{
builder.makeTitle("Greeting");
builder.makeString("朝から昼にかけて");
builder.makeItems(new String[]
{
"おはようございます。",
"こんにちは。"
});
builder.makeString("夜に");
builder.makeItems(new String[]
{
"こんばんは。",
"おやすみなさい。" ,
"さようなら。"
});
builder.close();
}
}
##TextBuilderクラス
TextBuilderクラスはBuilderの実装クラスです。
プレーンテキスト(普通の文字列)を使って文書を構築します。
結果はstringとして返します。
public class TextBuilder : Builder
{
private StringBuilder builder = new StringBuilder();
public void makeTitle(string title)
{
builder.Append("==============================\n");
builder.Append("『" + title + "』\n");
builder.Append("\n");
}
public void makeString(string str)
{
builder.Append("■" + str + "\n");
builder.Append("\n");
}
public void makeItems(string[] items)
{
for (int i = 0; i < items.Length; i++)
{
builder.Append("・" + items[i] + "\n");
}
builder.Append("\n");
}
public void close()
{
builder.Append("==============================\n");
}
public string getResult()
{
return builder.ToString();
}
}
##HTMLBuilderクラス
HTMLBuilderクラスもBuilderの実装クラスです。
HTMLを使って文書を構築します。
結果はHTMLファイルのファイル名として返します。
public class HTMLBuilder : Builder
{
private string fileName;
private StreamWriter writer;
public void makeTitle(string title)
{
fileName = title + ".html";
using (StreamWriter writer = File.CreateText(fileName))
{
this.writer = writer;
writer.WriteLine($"<html><head><title>{title}</title></head><body>");
writer.WriteLine($"<h1>{title}</h1>");
}
}
public void makeString(string str)
{
using (StreamWriter writer = new StreamWriter(fileName, true, Encoding.GetEncoding("shift-jis")))
{
writer.WriteLine($"<p>{str}</p>");
}
}
public void makeItems(String[] items)
{
using (StreamWriter writer = new StreamWriter(fileName, true, Encoding.GetEncoding("shift-jis")))
{
writer.WriteLine("<ul>");
for (int i = 0; i < items.Length; i++)
{
writer.WriteLine($"<li>{items[i]}</li>");
}
writer.WriteLine("</ul>");
}
}
public void close()
{
using (StreamWriter writer = new StreamWriter(fileName, true, Encoding.GetEncoding("shift-jis")))
{
writer.WriteLine("</body></html>");
}
}
public string getResult()
{
return fileName;
}
}
##Program
動作確認用のテストプログラムです。
コマンドラインで指定した形式に応じて文書を作成します。
コマンドラインでplainを指定した場合はTextBuilderクラスのインスタンスを、htmlを指定した場合にはHTMLBuilderクラスのインスタンスを、Directorクラスのコンストラクタに渡します。
TextBuilderもHTMLBuilderもBuilderのサブクラスであり、DirectorはBuilderのメソッドのみを使って文書を作ります。Builderのメソッドのみを使うということは、Directorは、実際に動いているのがTextBuilderなのかHTMLBuilderなのかを意識していないことになります。
なので、Builderは文書を構築するという目的を達成するのに必要かつ十分なメソッド群を宣言している必要があります。
ただし、プレーンテキストやHTMLファイルに固有のメソッドまでをBuilderが提供してはいけません。
public class Program
{
public static void Main(string[] args)
{
if (args.Length != 1)
{
Usage();
Environment.Exit(0);
}
if (args[0].Equals("plain"))
{
TextBuilder textBuilder = new TextBuilder();
Director director = new Director(textBuilder);
director.construct();
String result = textBuilder.getResult();
Console.WriteLine(result);
}else if (args[0].Equals("html"))
{
HTMLBuilder htmlbuilder = new HTMLBuilder();
Director director = new Director(htmlbuilder);
director.construct();
string filename = htmlbuilder.getResult();
Console.WriteLine(filename + "が作成されました。");
}
else
{
Usage();
Environment.Exit(0);
}
Console.ReadLine();
}
public static void Usage()
{
Console.WriteLine("Usage: C# Main plain プレーンテキストで文書作成");
Console.WriteLine("Usage: C# Main html htmlファイルで文書作成");
}
}
#実行結果
先のProgramを実行した結果が以下の通りです。
■プレーンテキスト
■HTMLファイル
<html><head><title>Greeting</title></head><body>
<h1>Greeting</h1>
<p>朝から昼にかけて</p>
<ul>
<li>おはようございます。</li>
<li>こんにちは。</li>
</ul>
<p>夜に</p>
<ul>
<li>こんばんは。</li>
<li>おやすみなさい。</li>
<li>さようなら。</li>
</ul>
</body></html>
#Builderパターンのメリット
このデザインパターンを使用するメリットは、Directorクラスが実際に利用するBuilderインターフェースの実装クラスを入れ替えることができる点にあります。
DirectorクラスはBuilderのメソッドを利用して文書を構成しますが、実際に利用しているクラスを知りません。
知らないからこそ、与えられたインスタンスによって同じ構造を持つ異なる表現方法のオブジェクトを作成することができるのです。
メリットについてのまとめは以下の通りです。
・複雑なオブジェクトの構築方法をカプセル化することができる
・生成するオブジェクトの内部表現をカプセル化することができる。
・DirectorクラスをBuilderの実装クラスに依存させないことで交換可能性(部品としての再利用性)が高まる
#Builderパターンのデメリット
デメリットについてのまとめは以下の通りです。
・Builderが提供するオブジェクトの生成手順は、増減が難しい(修正の影響範囲が子クラス全体に及ぶ)
・シンプルな構造のインスタンス生成に適応しても意味がない
・どのような情報を渡す必要性があるのか、ということをクライアント側が知っていなくてはならない