概要
@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。
※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。
本題
Builderパターン
第7回はBuilderパターンです。Builderパターンは「全体を構成する各部分を作り、段階を踏んで組み上げていく」ようなデザインパターンです。
サンプルコード
早速具体的な事例を見てみましょう。『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に掲載されているコードをC#で(大体)書き換えます。
// コンソールアプリケーションで実行を確認しました
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BuilderPattern
{
class Program
{
// Client
// ・Builderを利用する
static void Main(string[] args)
{
if (args.Length != 1)
{
Program.Usage();
Environment.Exit(0);
}
if (args[0].Equals("plain"))
{
TextBuilder textbuilder = new TextBuilder();
Director director = new Director(textbuilder);
director.Construct();
string result = textbuilder.Result;
Console.WriteLine(result);
}
else if (args[0].Equals("html"))
{
HTMLBuilder htmlbuilder = new HTMLBuilder();
Director director = new Director(htmlbuilder);
director.Construct();
string filename = htmlbuilder.Filename;
Console.WriteLine(filename + "が作成されました。");
}
else
{
Program.Usage();
Environment.Exit(0);
}
// 実行が一瞬で終わって確認できないので、キーの入力を待ちます
Console.ReadLine();
}
public static void Usage()
{
Console.WriteLine("Usage: C# Main plain プレーンテキストで文書作成");
Console.WriteLine("Usage: C# Main html htmlファイルで文書作成");
}
}
// Builder
// ・インスタンスを作成するためのインターフェース(API)を定める
// ・インスタンスの各部分を作るためのメソッドを用意する
public abstract class Builder
{
public abstract void MakeTitle(string title);
public abstract void MakeString(string str);
public abstract void MakeItems(string[] items);
public abstract void Close();
}
// Director
// ・Builderのインターフェース(API)を使ってインスタンスを生成する
// ・Builderのメソッドのみを使用する
public class Director
{
private Builder builder;
public Director(Builder builder)
{
this.builder = builder;
}
public void Construct()
{
this.builder.MakeTitle("Greeting");
this.builder.MakeString("朝から昼にかけて");
this.builder.MakeItems(new string[]
{
"おはようございます。",
"こんにちは。",
});
this.builder.MakeString("夜に");
this.builder.MakeItems(new string[]
{
"こんばんは。",
"おやすみなさい。",
"さようなら。",
});
this.builder.Close();
}
}
// ConcreteBuilder
// ・Builderのインターフェース(API)を実装する
// ・実際のインスタンス作成で呼び出されるメソッドを定義する
public class TextBuilder : Builder
{
private StringBuilder sb = new StringBuilder();
public string Result
{
get { return sb.ToString(); }
}
public override void MakeTitle(string title)
{
sb.Append("==============================\n");
sb.Append($"『{title}』");
sb.Append("\n");
}
public override void MakeString(string str)
{
sb.Append($"■{str}\n");
sb.Append("\n");
}
public override void MakeItems(string[] items)
{
for (int i = 0; i < items.Length; i++)
{
sb.Append($" ・{items[i]}\n");
}
sb.Append("\n");
}
public override void Close()
{
sb.Append("==============================\n");
}
}
// ConcreteBuilder
// ・Builderのインターフェース(API)を実装する
// ・実際のインスタンス作成で呼び出されるメソッドを定義する
public class HTMLBuilder : Builder
{
public string Filename { get; private set; }
private System.IO.StreamWriter writer;
public override void MakeTitle(string title)
{
this.Filename = title + ".html";
using (System.IO.StreamWriter writer = System.IO.File.CreateText(title))
{
this.writer = writer;
writer.WriteLine($"<html><head><title>{title}</title></head><body>");
writer.WriteLine($"<h1>{title}</h1>");
}
}
public override void MakeString(string str)
{
using (System.IO.StreamWriter writer = new System.IO.StreamWriter(this.Filename, true, Encoding.GetEncoding("utf-8")))
{
writer.WriteLine($"<p>{str}</p>");
}
}
public override void MakeItems(string[] items)
{
using (System.IO.StreamWriter writer = new System.IO.StreamWriter(this.Filename, true, Encoding.GetEncoding("utf-8")))
{
writer.WriteLine("<ul>");
for (int i = 0; i < items.Length; i++)
{
writer.WriteLine($"<li>{items[i]}</li>");
}
writer.WriteLine("</ul>");
}
}
public override void Close()
{
using (System.IO.StreamWriter writer = new System.IO.StreamWriter(this.Filename, true, Encoding.GetEncoding("utf-8")))
{
writer.WriteLine("</body></html>");
}
}
}
}
実行結果
・プレーンテキスト
==============================
『Greeting』
■朝から昼にかけて
・おはようございます。
・こんにちは。
■夜に
・こんばんは。
・おやすみなさい。
・さようなら。
==============================
・HTMLファイル
<p>朝から昼にかけて</p>
<ul>
<li>おはようございます。</li>
<li>こんにちは。</li>
</ul>
<p>夜に</p>
<ul>
<li>こんばんは。</li>
<li>おやすみなさい。</li>
<li>さようなら。</li>
</ul>
</body></html>
<p>朝から昼にかけて</p>
<ul>
<li>おはようございます。</li>
<li>こんにちは。</li>
</ul>
<p>夜に</p>
<ul>
<li>こんばんは。</li>
<li>おやすみなさい。</li>
<li>さようなら。</li>
</ul>
</body></html>
効能
- DirectorクラスをConcreteBuilderに依存させないことで交換可能性(部品としての再利用性)が高まる。
- インスタンスの作成過程をコントロールできる
- コンストラクタを簡素化できる
- 1つのコンストラクタで表現しようとすると引数が複雑になったり、
使用上の注意
- 修正するときに役割を見極める。Builderクラスを修正するとDirectorが呼び出すメソッドとConcreteBuilder全てに影響する。Directorから直接ConcreteBuilderを呼び出すと部品の独立性が損なわれ、他のConcreteBuilderに影響が出ることもある。
- Builderクラスにはインスタンス生成に必要十分なメソッドを群を用意する。ConcreteBuilderの追加に耐えうる設計にする。
関連しているパターン
- TemplateMethodパターン
- Compositeパターン
- Abstract Factoryパターン
- Facadeパターン
感想や疑問
- 生成するインスタンスのクラスの中にBuilderを書いてしまうやり方もある(が、再利用性はない)
- 生成するインスタンスが複数でも、生成方法が単純ならわざわざ使わなくてよさそう
JavaとC#
- What is the .NET equivalent of StringBuffer in Java?
- PrintWriter (java), StreamReader(C#)
- What is the C# equivalence for the JAVA
System.exit(0);
? [duplicate]
C#で学ぶデザインパターン入門
①Iterator
②Adapter
③Template Method
④Factory Method
⑤Singleton
⑥Prototype
⑦Builder
⑧AbstractFactory
⑨Bridge
⑩Strategy
⑪Composite Pattern
⑫Decorator Pattern