今天你要為一個遊樂園設計程式,讓客人可以自由選擇旅館及各種門票、餐廳訂位、或是其他特殊活動來建立自己的假期計畫,但你可能會遇到一些問題:每個客人的假期計畫都不一樣,如旅行天數不同,或是想住的旅館會不同,而且為了要滿足客製化假期內容的要求,你的設計可能會有很多建構子才能滿足需求:
這樣對於客戶而言,他需要先知道全部的建構子有哪些,才能知道要選用哪個來建立適合自己的假期物件。但有時候我們提供的的建構子可能還是不符合客戶的要求,因此我們需要有一個有彈性的結構,可以把整個假期物件的產生過程封裝起來,而且客戶還可以不影響物件建立的步驟,建立出自己想要的假期物件,這就是這次要介紹的建立者模式!
看到這裡,可能有些人會覺得,同樣都是建立物件,那直接 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(List tickets);
// 要提供一個方法讓使用者能取得假期物件
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

留言
張貼留言