今天你要為一個遊樂園設計程式,讓客人可以自由選擇旅館及各種門票、餐廳訂位、或是其他特殊活動來建立自己的假期計畫,但你可能會遇到一些問題:每個客人的假期計畫都不一樣,如旅行天數不同,或是想住的旅館會不同,而且為了要滿足客製化假期內容的要求,你的設計可能會有很多建構子才能滿足需求:
這樣對於客戶而言,他需要先知道全部的建構子有哪些,才能知道要選用哪個來建立適合自己的假期物件。但有時候我們提供的的建構子可能還是不符合客戶的要求,因此我們需要有一個有彈性的結構,可以把整個假期物件的產生過程封裝起來,而且客戶還可以不影響物件建立的步驟,建立出自己想要的假期物件,這就是這次要介紹的建立者模式!
看到這裡,可能有些人會覺得,同樣都是建立物件,那直接 Abstract Factory 來建立物件不就好了?這裡要注意的是, Builder 著重在隱藏複雜物件生成的步驟,且生成的物件(通常是複雜物件) 彼此會有「部份」(Part of)的概念。而 Abstract Factory 則是著重在管理有關聯性的物件,但這些物件不會有「部份」(Part of)的概念。實務上這個模式還滿常被使用到的,如 JAVA SDK 裡的 StringBuilder,StringBuffer,以及 Android SDK 裡的 AlertDialog.Builder,有興趣的人可以參考看看。
參考資料:
深入淺出設計模式(Head First Design Patterns)
白話 Design Pattern (四) Builder pattern
public class Vocation { // 客人只要求天數,其他隨便排 public Vocation(Date begin, Date end) { // ... } // 客人要求天數,還指定飯店 public Vocation(Date begin, Date end, Hotel hotel) { // ... } // 客人指定天數,飯店,還有餐廳 public Vocation(Date begin, Date end, Hotel hotel, Restaurant restaurant) { // ... } // 可能還有其他可以讓客人選擇的建構子 }
我們先來看看建立者模式的定義及類別圖吧
將一個複雜對象的建構和表現分離,使得同樣的建構過程可以產生出不同的表現
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
Separate the construction of a complex object from its representation so that the same construction process can create different representations.
從類別圖及定義可以知道,Builder 就是定義物件產生的一個介面,通常會定義一系列的方法,通常是 setXXX / addXXX,可以讓使用者來更改想要的物件行為。物件建構的過程是隱藏的,使用者是碰不到的。因為上述兩點,才符合定義說的相同建構過程可以有不同表現。Director 就是實際上調用這個介面來產生物件的中介者,主要的工作是指導 Builder 產生出特定的物件,client 要自己調用 Builder 來取得物件當然也可以。ConcreteBuilder 就是 Builder 介面的實作,可以產生真正產品的類別。
接著就來看看上述的範例使用了建立者模式的程式碼吧
// Builder 介面 // 實務上 Builder 會搭配 Fluent interface // 讓程式更有可讀性 public interface VocationBuilder { // 指定假期開始時間 public VocationBuilder setBeginDate(String date); // 指定假期結束時間 public VocationBuilder setEndDate(String date); // 指定住哪間飯店 public VocationBuilder setHotel(Hotel hotel); // 指定吃哪間餐廳 public VocationBuilder setRestaurant(Restaurant restaurant); // 指定要玩哪些景點的門票 public VocationBuilder setTicket(Listtickets); // 要提供一個方法讓使用者能取得假期物件 public Vocation create(); } // 能產生三天假期規劃的 Builder, 實作上面的 Builder // 這裡只是範例, 實際上可以依需求有不同實作 public class ThreeDayVocationBuilder implements VocationBuilder { // 保留使用者想要的客製化, // 就是使用者想要的 "表現" private String mBeginDate; private String mEndDate; private Hotel mHotel; private Restaurant mRestaurent; private List mTickets; @Override public VocationBuilder setBeginDate(String date); { mBeginDate = date; return this; } @Override public VocationBuilder setEndDate(String date) { // 這邊可以加上些判斷, 確認使用者傳入的參數 // 確實是三天後,或是自動幫使用者調整 mEndDate = date; return this; } @Override public VocationBuilder setHotel(Hotel hotel) { mHotel = hotel; return this; } @Override public VocationBuilder setRestaurant(Restaurant restaurant) { mRestaurant = restaurant; return this; } @Override public VocationBuilder setTicket(List tickets); { mTickets = tickets; return this; } @Override public Vocation create() { // 回傳真正的假期物件給使用者 // 省略了 Vocation 的程式碼 // 這邊只是範例, 實務上可能物件的產生很繁瑣 return new Vocation(mBeginDate, mEndDate, mHotel, mRestaurent, mTickets); } } // 使用上可以這樣使用 Builder VocationBuilder builder = new ThreeDayVocationBuilder(); builder.setBeginDate("2018/01/01"); builder.setEndDate("2018/01/03"); builder.setHotel(someHotel); // 懶得寫 hotel 物件 Vocation vocation = builder.build() // 這樣就能取得 Vocation // 也能搭配使用 Fluent interface Vocation vocation = new ThreeDayVocationBuilder() .setBeginDate("2018/01/01") .setEndDate("2018/01/03") .setHotel(someHotel) .create()
最後來說說總結吧,建立者模式的特點就是將複雜物件的產生過程隱藏起來,使用者無法碰到,且允許物件用多個步驟建立 (跟 Factory Method 只有一個步驟不同),因為它的特性,因此經常用來建立合成結構。但對於使用者而言,要是不知道有哪些 setXXX() 方法可以用,也無法建立出想要的物件,這是要注意的地方。
深入淺出設計模式(Head First Design Patterns)
白話 Design Pattern (四) Builder pattern
留言
張貼留言