【デザインパターン】ビルダーパターン
研究のスライド作ってたら26時で焦って書き始めました。
参考書
さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!
※ただし、Builderパターンについては軽く触れる程度しか載っていません
Builderパターンとは?
インスタンスの生成時に必要な情報を、与えられたコンテキストによって肩代わりしてくれるデザインパターンです。
「インスタンスの生成を補助してくれるとかFactoryパターンと似てないか?」と感じた方も多いと思いますが、Factoryはインスタンスの生成に関して全てを秘匿します。
どのインスタンスを作りたいか指定した関数を呼び出すだけで必要なインスタンスが生成され、生成時にどのようなパラメタ(コンストラクタ引数)が渡されるかを知る必要はありません。
一方、Builderパターンはインスタンスの生成は秘匿されていますが、どのようなパラメタが渡されるかを知っている必要性があります。
しかし、それによってパラメタを柔軟に変えられるため、コンストラクタに多数の引数が必要であったり、動的に引数の数が変わる場合(オプションが存在する場合)に力を発揮することが出来ます。
さて、いつもどおりWikipedia大先生ではどう解釈しているのでしょうか?
非常にシンプルですね。
「動的」なオブジェクト生成ってところが味噌だと思います。
Builderパターンのメリット
Builderパターンのデメリット
いいことばかりではなく、使い方を誤ると力を発揮できないパターンです。
特にインスタンスの生成を抽象化してくれるFactoryパターンとよく比較されます。
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パターンの方が上であるということ