dorivenの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】ビルダーパターン

研究のスライド作ってたら26時で焦って書き始めました。

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!

※ただし、Builderパターンについては軽く触れる程度しか載っていません

Builderパターンとは?



インスタンスの生成時に必要な情報を、与えられたコンテキストによって肩代わりしてくれるデザインパターンです。
インスタンスの生成を補助してくれるとかFactoryパターンと似てないか?」と感じた方も多いと思いますが、Factoryはインスタンスの生成に関して全てを秘匿します。
どのインスタンスを作りたいか指定した関数を呼び出すだけで必要なインスタンスが生成され、生成時にどのようなパラメタ(コンストラクタ引数)が渡されるかを知る必要はありません。
一方、Builderパターンはインスタンスの生成は秘匿されていますが、どのようなパラメタが渡されるかを知っている必要性があります。
しかし、それによってパラメタを柔軟に変えられるため、コンストラクタに多数の引数が必要であったり、動的に引数の数が変わる場合(オプションが存在する場合)に力を発揮することが出来ます。

さて、いつもどおりWikipedia大先生ではどう解釈しているのでしょうか?

オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする。
http://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Builder_UML_class_diagram.svg/700px-Builder_UML_class_diagram.svg.png

非常にシンプルですね。
「動的」なオブジェクト生成ってところが味噌だと思います。

Builderパターンのメリット


  • 複雑なオブジェクトの構築方法をカプセルできる
  • 複数の変化するプロセスで、オブジェクトを構築できます(オプション機能)
  • 製品の内部表現をカプセル化することもできる
  • クライアントには中小インタフェースしか見えないため、交換が容易になる

Builderパターンのデメリット



いいことばかりではなく、使い方を誤ると力を発揮できないパターンです。
特にインスタンスの生成を抽象化してくれるFactoryパターンとよく比較されます。

  • シンプルな構造のインスタンス生成に適応しても、意味がなく工数が増えてしまう
  • どのような情報を渡す必要性があるのか、ということをクライアント側が知っていなくてはならない*1

Factoryパターンとの使い分けをしっかりと意識する必要性がありそうですね。

具体的な例


あたたはオブジェクト町の郊外にある新しいテーマパーク「パターンズランド」の休暇プランナの構築を依頼されています。テーマパークのゲストは、ホテルやさまざまなタイプの入場券を選んだり、レストランや特別なイベントの予約をしたりすることができます。休暇プランナを作成するためには、以下のような構造の作成を可能にしなければいけません。

Directorはもちろんオブジェクトの使用者なので「あなた」、
ConcreteBuilderは「休暇プランナを作成するため」のもの、
ということなります。

では、実際にBuilderパターンがどのように使われるかを見て行きましょう。

使用方法



※コードがほとんど載っていないため、かなりの憶測で書いています

まずは抽象的なBuilderパターンを書いてきましょう。
ある日付について、ホテル、食事、イベント、チケットについてのオプションを裁く関数を用意しています。

interface AbstractBuilder
{
	// 必須
	private final String day;
	// オプション
	private ArrayList<String> hotelNameList;
	private ArrayList<String> targetList;
	private ArrayList<String> eventNameList;
	private ArrayList<String> flightNameList;

	AbstractBuilder addHotel(String hotelName);
	AbstractBuilder addReservation(String target);
	AbstractBuilder addSpecialEvent(String eventName);
	AbstractBuilder addTickets(String flightName);
	AbstractBuilder getVacationPlanner();
}

予約対象のリストを保持するだけのクラスです。

それでは、このインタフェースを元にして具体的な動作を定義していきましょう。

class VacationBuilder implements AbstractBuilder
{

	public VacationBuilder(String day)
	{
		this.day = day;
	}

	public AbstractBuilder addHotel(String hotelName)
	{
		this.hotelName = hotelName;
		return this;
	}

	public AbstractBuilder addReservation(String target)
	{
		this.targetList.add(target);
		return this;
	}

	public AbstractBuilder addSpecialEvent(String eventName)
	{
		this.eventNameList.add(eventName);
		return this;
	}

	public AbstractBuilder addTickets(String flightName)
	{
		this.flightNameList.add(flightName);
                return this;
	}

	public AbstractBuilder getVacationPlanner()
	{
		return new Planner(this);
	}
}

ここで重要なのはadd~というメソッドではインスタンス自身を返している点です。
また、getVacationPlanner()でBuildしたい対象のインスタンスを自身を引数に渡して返すようにしています。
こうすることで、インスタンス生成に関しての手法を隠蔽することが出来ます。

では、これを実際に使ってみましょう。

class Clinet
{
	public static int main(String args[])
	{
		Client client = new Client(); // Director
		client.constructPlanner();
	}

	
}

class Director
{
	public constructPlanner()
	{
		String day = "2014/11/29";
		AbstractBuild builder = new VacationBuilder(day).
			addHotel("Grand Facadian").
			addTickets("Patterns on Ice");
		Planner yourPlanner = builder .getVacationPlanner();
	}
}

class Planner
{
	private HotelReserver hotelReserver;
	private RestaurantReserver restaurantReserver;
	private SpecialEventReserver specialEventReserver;
	private TicketReserver ticketReserver;

	Planner(AbstractBuilder builder)
	{
		String day = builder.day;
		// 下の関数は本来なら他のタイミングで呼び出すべきだが、ソースコードが肥大化しそうなのでここに収めました
		hotelReserver.reserve(day, hotelNameList);
		restaurantReserver.reserve(day, targetList);
		specialEventReserver.reserve(day, eventNameList);
		ticketReserver.reserve(day, flightNameList);
	}

        // 本来はここにBuilderを追加します。そうすることでbuilderの情報にアクセス出来るようになるからです。
        class AbstractBuilder{...}
        class VacationBuilder{...}
}

HotelReserverとかはReserverインターフェースを作成して予約などを行ってくれるクラスを想定しています(勝手に憶測で作ったクラスなので参考にはしないでね)

Directorがどのようにインスタンスを生成するかを伝達し(結婚式場でいう受付みたいな感じですね)必要なオブジェクトを渡してくれます。
こうすることで、動的にPlannerのインスタンスを生成させることが出来るようになっています。

すみません、ちょっと駆け足になってしまいましたが今日はこのへんでおしまいにしたいと思います。
最後まで読んでいただきありがとうございました。

*1:つまり、秘匿性の高さにおいてはFactoryパターンの方が上であるということ