7. Builder パターン
デザインパターン 7章 Builder パターン
7.1 Builder パターンとは第7章では、Builder パターンを学びます。builder とは、建築者や建築業者などを意味する単語です。Builder パターンとは、同じ作成過程で異なる表現形式の結果を得るためのパターンです。 例えば、家を建てることを考えてみます。完成する家がどのような家になるかというのは「家の構築過程」と「素材」大きく2つの要素で決定されると考えてみてください。「作成過程」とは、「どのような順番で、どこに何を配置していくか」というようなことであり、「素材」とは、「柱には何を使って、壁には何を使って・・・」ということであると考えてください。 このとき、「作成過程」には、"平屋を建てるための作成過程" や "2階建ての家を建てるための作成過程"、または "少し変わった平屋を建てるための作成過程" など様々なものが考えられます。同様に、「素材」にも、"柱は木で、壁は土壁、屋根は瓦" などのような和風の家を建てるための素材を用いることもあれば、"柱は鉄で壁と屋根はコンクリート" といった場合も考えられます。 これらをそれぞれ用意しておくことで、『 "ちょっと変わった平屋を建てる作成過程" で "柱は鉄で壁と屋根はコンクリート" の家を建ててください』という要望に柔軟に応えることができるようになります。 Builder パターンとは、このような、「作成過程」を決定する Director と呼ばれるものと「表現形式」を決定する Builder と呼ばれるものを組み合わせることで、オブジェクトの生成をより柔軟にし、そのオブジェクトの「作成過程」をもコントロールすることができるようにするためのパターンです。 7.2 サンプルケースサンプルケースでは、理科の実験で、食塩水と砂糖水を作ることを考えましょう。 食塩水 をあらわすクラスは以下のソースコードで与えられているものとします。(砂糖水をあらわすクラスも同様とする)
public class SaltWater{
public double salt;
public double water;
public SaltWater(double water,double salt){
this.salt = salt;
this.water = water;
}
}
食塩水を作るのなんてとても簡単だとお思いでしょう。しかし、侮ってはいけません。余計なことをいろいろしながら作る必要がある場合もあるのです。たとえば数学の問題には、以下のような問題がよく出題されます。 ■問題1■ 100g の水に 40g の食塩を溶かして作成した食塩水のうち 70g を捨てた後、 水を 100g 追加し、最後に食塩を 15g 溶かすと 何% になるでしょう? 上記のような食塩水オブジェクトのインスタンスを作成するためには、以下のようにします。 SaltWater saltWater = new SaltWater( 100 , 40 ); saltWater.salt *= (1 - 70 / (saltWater.salt + saltWater.water)); saltWater.water *= (1 - 70 / (saltWater.salt + saltWater.water)); saltWater.water += 100; saltWater.salt += 15; たいていの場合、このような方法で解決されるでしょう。しかし、場合によっては、もっと柔軟に食塩水オブジェクトを得たいと思うことがあるかもしれません。さまざまな要求が考えられますが、Builderパターンを利用することで、以下のような要求に応えることができるようになります。
このような要求に応えるため、Builder パターンでは、Director と Builder となるクラスを作成します。1節でもお話ししたように、Director の役割は「作成過程」を決定することで、Builder の役割は「表現形式」を決定することです。サンプルケースでは、Director の役割は、「100g の溶媒に 40g の溶質を溶かし、うち 70g を捨てた後、溶媒を 100g 追加し、最後に溶質を 15g 加える」ということを決定することです。また、Builder の役割は、「溶媒に水、溶質に食塩」を使うことを決定することです。クラス図にすると、以下のようになります。 ![]() それでは、ソースコードを見てみましょう。まずは Builder インタフェースです。
public interface Builder{
public void addSolute(int soluteAmount);
public void addSoluvent(int solventAmount);
public void abandonSolution(int solutionAmount);
public Object getResult();
}
Builder インタフェースでは、溶質を追加するための addSolute メソッド、溶媒を追加するための addSoluvent メソッド、溶液を捨てるための abandonSolution メソッド、そして、生成物を得るための、getResult メソッドを定義しています。次に、Director クラスを見てみます。Director クラスでは Builder インタフェースを利用して、「作成過程」にのっとって、インスタンスを組み立てていきます。
public class Director{
private Builder builder;
public Director(Builder builder){
this.builder = builder;
}
public void constract(){
builder.addSolvent( 100 );
builder.addSolute( 40 );
builder.abandonSolution( 70 );
builder.addSolvent( 100 );
builder.addSolute( 15 );
}
}
Director クラスは、Builder インタフェースを実装するものが与えられるということを知っているだけで、実際には、どの Builder 実装クラスが渡されるのかということを知っている必要がありません。これにより、Builder のすげ替えが簡単に行えるようになっているのです。Builder実装クラスである、SaltWaterBuilder クラスを見てみましょう。
public class SaltWaterBuilder implements Builder{
private SaltWater saltWater;
public SaltWaterBuilder(){
this.saltWater = new SaltWater(0,0);
}
public void addSolute(int saltAmount){
saltWater.salt += saltAmount;
}
public void addSoluvent(int waterAmount){
saltWater.water += waterAmount;
}
public void abandonSolution(int saltWaterAmount){
saltWater.salt *= 1 - saltWaterAmount / (saltWater.salt + saltWater.water);
saltWater.water *= 1 - saltWaterAmount / (saltWater.salt + saltWater.water);
}
public SaltWater getResult(){
return this.saltWater;
}
}
このような設計にしておくことで、Director と Builder を自由に組み合わせ、より柔軟にインスタンスを生成することができるようになります。実際の生成手順は以下のようになります。 Builder builder = new SaltWaterBuilder(); Director dir = new Director( builder ); dir.constract(); SaltWater saltWater = (SaltWater)builder.getResult(); 砂糖水を生成するための SugarWaterBuilder を用意しておけば、同じ「作成過程」で作成される砂糖水を得ることも簡単にできます。 ![]() 例えば、XML を parse する Director と、HTML用に出力する HTMLBuilder、プレーンテキストを出力する PlaneBuilder などを用意しておくことで、同じ文書を要求に合わせて、異なる表現形式で出力することができるようになります。 7.3 Builder パターンのまとめBuilder パターンの一般的なクラス図を以下に示します。 ![]() |
![]()
![]()
|