Builderパターン


 ★★☆

Builderパターン

同じ作成過程で異なる表現形式の結果を得る

Builder パターンとは、「処理手順」を決定する Director と呼ばれるものと「処理内容」を決定する Builder と呼ばれるものを組み合わせることで、オブジェクトの生成をより柔軟にし、そのオブジェクトの「処理手順」をもコントロールすることができるようにするためのパターンです。



例えば、社員旅行することを考えてみます。どのような旅行になるかは「旅行の手順」と「旅行の内容」の大きく2つの要素で決定されます。「旅行の手順」とは、「旅行はどこに、どのような順番で、何をどのようにしていくか」というようなことであり、「旅行の内容」とは、「移動には何を使って、ホテルはどこに泊まって・・・」ということであると考えてください。



このとき、「旅行の手順」には、“観光旅行のための手順” や “食べ歩きのための手順”、または “史跡見学のための手順” など様々なものが考えられます。同様に、「旅行の内容」にも、“移動は新幹線で、宿泊は豪華ホテルで” などのような費用の掛かる富豪コースもあれば、“移動はバスで、ホテルは激安で”といった貧民コースも考えられます。


これらをそれぞれ用意しておくことで、 “観光旅行のための手順” で “富豪コース” とか“食べ歩きのための手順” で “貧民コース”といったように、あらかじめ用意された「旅行の手順」と「旅行の内容」を組み合わせることによって、いろいろな要望に柔軟に応えることができるようになります。


オブジェクト指向プログラミングでは、「誰が何を知っているか」はとても大切です。すなわち、どのクラスがどのメソッドを使ってよいかに注意してプログラミングする必要があります。

Builder パターンでは、利用者のクラス(main)は、Builder クラスのメソッドを知りません。つまり、呼び出しません。Director クラスのあるメソッド(construct)のみ呼び出します。すると、Director クラスの中で処理が進み、作業が完了します。

一方、 Director クラスが知っているのは、Builder クラスです。Director クラスは Builder クラスのメソッドを使って作業を進めます。しかし、Director クラスは、自分が実際に利用しているクラスが「本当は」何なのかを知りません。 つまり、Builder クラスを継承したサブクラスであるということしか知らないのです。しかし、知らなくてもかまわないのです、Director クラスは、Builder クラスのメソッドだけを使っており、 Builder クラスのサブクラスはそのメソッドを実装しているからです。

例を挙げてもう一度説明しましょう。「旅行の手順」という Director クラスは、「旅行の内容」という Builder クラスのメソッドだけを知っています。例えば、(書いてないけど)交通機関を得るメソッド、ホテルを得るメソッドなどです。しかし、実際はどのコースなのかということは知らなくてもかまわないのです。「富豪コース」であろうが、「貧民コース」であろうが、同じメソッドを実装しているからです。

Director クラスは、Builder のサブクラスが何であるということは知りません。逆に言うと、知らないからこそ、入れ替えできるのです。サブクラスが何であるということ知っていて、その固有のメソッドを使用していては、もはや入れ替えはできません。

知らないからこそ、入れ替えができるのです。入れ替えられるからこそ、部品としての価値が高くなるのです。この「交換可能性」については、クラスを設計するときに常に意識しておくことが必要になります。


しかし、いくら厳密に設計したとしても、修正や追加は発生します。クラスの役割を理解していないと、どのクラスを変更すべきかの判断を誤ります。例えば、 Builder クラスを修正するということは、 Director クラスが呼び出すメソッドを修正することであり、また、 Builder クラスのサブクラス全部に影響が及ぶということです。あるいは、うっかり、 Director クラスがBuilder クラスのあるサブクラスに固有のメソッドを呼び出しすると、部品としての独立性が失われ、他のサブクラスに切り替えたときにうまく動かないということになってしまいます。



例題

あらかじめ用意された文書を指定されたフォーマットに整形するクラスを作成しなさい。

ひとつはプレーンなテキストにする TextBuilder クラス、もうひとつは HTML にする HtmlBuilder クラスです。


実行結果 (一部分です)
プレーンなテキスト
*** いろいろな国の言葉 ***
■こんにちは
 ○Hello
 ○Bonjour
  :
(end)
    HTML
<h3>いろいろな国の言葉</h3>
<span>こんにちは</span>
<ul>
 <li>Hello</li>
 <li>Bonjour</li>
  :
</ul>












Builderパターンを使用しない例

AbstractContent.java
public abstract class AbstractContent { protected StringBuffer buf = new StringBuffer(); protected final String title = "いろいろな国の言葉"; protected final String[] japanese = new String[] { "こんにちは", "ありがとう" }; protected final String[][] flangs = new String[][] { { "Hello", "Bonjour", "Guten Tag", "Ciao", "Hola" }, { "Thank you", "Merci", "Danke", "Grazie", "Gracias" } }; protected abstract void makeTitle(String title); protected abstract void makeJapanese(String japanese); protected abstract void makeFlangs(String[] flangs); protected abstract void close(); public String get() { return buf.toString(); } public void construct() { makeTitle(title); for (int i = 0; i < japanese.length; i++) { makeJapanese(japanese[i]); makeFlangs(flangs[i]); } close(); } }
TextBuilder.java
public class TextBuilder extends AbstractContent { protected void makeTitle(String title) { buf.append("*** " + title + " ***\n"); } protected void makeJapanese(String japanese) { buf.append("■" + japanese + "\n"); } protected void makeFlangs(String[] flangs) { for (int i = 0; i < flangs.length; i++) buf.append(" ○" + flangs[i] + "\n"); } protected void close() { buf.append("(end)\n"); } }
HtmlBuilder.java
public class HtmlBuilder extends AbstractContent { protected void makeTitle(String title) { buf.append("<html>\n<head>\n<title>" + title + "</title>\n</head>\n<body>\n"); buf.append("<h3>" + title + "</h3>\n"); } protected void makeJapanese(String japanese) { buf.append("<span>" + japanese + "</span>\n"); } protected void makeFlangs(String[] flangs) { buf.append("<ul>\n"); for (int i = 0; i < flangs.length; i++) buf.append("<li>" + flangs[i] + "</li>\n"); buf.append("</ul>\n"); } protected void close() { buf.append("</body>\n</html>\n"); } }
Main.java
public class Main { public static void main(String[] args) { TextBuilder txt = new TextBuilder(); txt.construct(); System.out.print(txt.get()); HtmlBuilder htm = new HtmlBuilder(); htm.construct(); System.out.print(htm.get()); } }

Builderパターンを使用した例

AbstractDirector.java
public abstract class AbstractDirector { protected AbstractBuilder builder; public AbstractDirector() {} public AbstractDirector(AbstractBuilder builder) { this.builder = builder; } public abstract void construct(); }
Director.java
public class Director extends AbstractDirector { private final String title = "いろいろな国の言葉"; private final String[] japanese = new String[] { "こんにちは", "ありがとう" }; private final String[][] flangs = new String[][] { { "Hello", "Bonjour", "Guten Tag", "Ciao", "Hola" }, { "Thank you", "Merci", "Danke", "Grazie", "Gracias" } }; public Director(AbstractBuilder builder) { super(builder); } public void construct() { builder.makeTitle(title); for (int i = 0; i < japanese.length; i++) { builder.makeJapanese(japanese[i]); builder.makeFlangs(flangs[i]); } builder.close(); } }
AbstractBuilder.java
public abstract class AbstractBuilder { protected StringBuffer buf = new StringBuffer(); protected abstract void makeTitle(String title); protected abstract void makeJapanese(String japanese); protected abstract void makeFlangs(String[] flangs); protected abstract void close(); public String get() { return buf.toString(); } }
TextBuilder.java
public class TextBuilder extends AbstractBuilder { protected void makeTitle(String title) { buf.append("*** " + title + " ***\n"); } protected void makeJapanese(String japanese) { buf.append("■" + japanese + "\n"); } protected void makeFlangs(String[] flangs) { for (int i = 0; i < flangs.length; i++) buf.append(" ○" + flangs[i] + "\n"); } protected void close() { buf.append("(end)\n"); } }
HtmlBuilder.java
public class HtmlBuilder extends AbstractBuilder { protected void makeTitle(String title) { buf.append("<html>\n<head>\n<title>" + title + "</title>\n</head>\n<body>\n"); buf.append("<h3>" + title + "</h3>\n"); } protected void makeJapanese(String japanese) { buf.append("<span>" + japanese + "</span>\n"); } protected void makeFlangs(String[] flangs) { buf.append("<ul>\n"); for (int i = 0; i < flangs.length; i++) buf.append("<li>" + flangs[i] + "</li>\n"); buf.append("</ul>\n"); } protected void close() { buf.append("</body>\n</html>\n"); } }
Main.java
public class Main { public static void main(String[] args) { AbstractDirector director = null; TextBuilder txt = new TextBuilder(); director = new Director(txt); director.construct(); System.out.print(txt.get()); HtmlBuilder htm = new HtmlBuilder(); director = new Director(htm); director.construct(); System.out.print(htm.get()); } }

この例は Template Method パターンで書かれています。

処理の枠組みを記述したスーパークラスが AbstractContent で、それを継承して処理の具体的内容を記述したのが TextBuilder と HtmlBuilder です。インスタンスの生成はTextBuilder と HtmlBuilder について行われ、それらのクラスにある、AbstractContent から継承した construct() メソッドを呼び出しています。

しかし、 TextBuilder と HtmlBuilder は、処理手順の書かれた AbstractContent のサブクラスとして作成されていますので、処理手順と表現方法を自由に組み合わせたり変更したりができません。 例えば、HTML文章を今は「処理手順1」で出していたけど、次は「処理手順5」で出そうというようなことができないわけです。

処理手順である AbstractContent を継承して、TextBuilder はプレーンテキストで出す、HtmlBuilder はHTMLで出す、としているので、処理手順は変更できないのです。

「作成過程」を決定する Director と呼ばれるものと「表現形式」を決定する Builder と呼ばれるものを組み合わせて実現しています。

Template Method パターンと同様、 Director には処理の枠組みが記述されており、 Builder には処理の具体的内容が記述されています。

ただし、 Template Method パターンと異なるところは、インスタンスの生成が両方について行われることです。まず、Builder を継承した TextBuilder や HtmlBuilder のインスタンスが生成され、Director のインスタンス生成時にそれらが引き渡されます。そして、 Director クラスの construct() メソッドを呼び出します。

そして、引き渡されたTextBuilder や HtmlBuilder のインスタンスに従った文書が作成されるわけです。例えば、 HtmlBuilder のインスタンスがDirector に渡された場合は、 Director から呼ばれている makeTitle、makeJapanese、makeFlangs、close はすべてHtmlBuilder のものが使われ、HTML文書が出来上がることになります。

Builderパターンでは、Template Method パターンと違って、処理手順を変更できます。Director クラスの代わりに別の処理手順が書かれたクラスのインスタンスを生成すればよいからです。


それでは Director クラスを別のものに替えて見ましょう。

Director.java
public class Director extends AbstractDirector { private final String title = "中国語"; private final String japanese = "ありがとう"; private final String[] flangs = new String[] { "謝謝", "多謝", "唔該" }; public Director(AbstractBuilder builder) { super(builder); } public void construct() { builder.makeTitle(title + "の" + japanese); builder.makeFlangs(flangs); builder.close(); } }


この Director クラスでは、次のように表示されます。

実行結果 (一部分です)
プレーンなテキスト
*** 中国語のありがとう ***
   ○謝謝
   ○多謝
   ○唔該
(end)
  HTML
<h3>中国語のありがとう</h3>
<ul>
<li>謝謝</li>
<li>多謝</li>
<li>唔該</li>
</ul>