設計模式—建造者模式 (Builder Design Pattern)
客製化種種零件以打造複雜物件
建造者模式在幹嘛?
建造者 (Builder) 模式將物件的「建構」與「表示」分離,隱藏並封裝建構過程的細節。它讓我們可以將物件本身拆解成不同的元件,一步一步建造每一部分,最後產生出我們想要的複雜物件。
Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
為什麼要用建造者模式?
要解決的問題為:
- 拖太長的建構子 (telescoping constructor)
- 建構子內不需要的參數必須放 null
以下我們先用一個蓋房子的例子,來觀察這些問題。
蓋一間房子
假設我要創造一個房子的物件,那最簡單粗暴的方法就是定義一個物件 House
、賦予它房子該有的屬性 (property):窗戶數、門數、房間數,並使用建構子 (constructor) 來帶入這些參數。
以 pseudocode 表示:
House(doorNum, windowNum, roomNum)
蓋許多間不同的房子
今天假設我想要創建不同的房子:有花園的房子、有泳池的房子、有車庫的房子等等呢?
簡單,我們都知道可以把 House
變成 base class,再讓其他子類別繼承它,打造不同的房子。
這時為了記錄這些房子多出來的功能,我的 House
物件的屬性就要擴增成:窗戶數、門數、房間數、有無花園、有無泳池、有無車庫。
House(doorNum, windowNum, roomNum, hasGarden, hasPool, hasGarage)
那假設我這個 House
物件要給更多房子用:有雕像的房子、有健身房的房子、有爐火的房子等等,那這物件的參數會拖得很長。這顯然不是一個好的解法。
House(doorNum, windowNum, roomNum, hasGarden, hasPool, hasGarage, hasStatue, hasGym, hasFireStove)
因為當我要創造一個房子時,我很難一目瞭然每個建構子代表的意義:
new House(2, 4, 3, true, true, true, false, false, false)
而且如果今天我可能有一系列的房子沒有爐火管線、也禁止蓋健身房,那可能對於這些房子我都要把某些參數設為 null,增加更多的混亂:
new House(2, 4, 3, true, true, true, false, null, null)
Builder: 客製化蓋房子的種種零件
建造者模式用一種 configuration 的方式,拆解每個元件建造的過程,避免有拖得很長的建構子,使建構的意圖清晰許多。
因此剛剛那些房子,實現建造者模式的程式碼可能會長得像這樣:
HouseBuilder.SetDoorNum(2);
HouseBuilder.SetWindowNum(4);
HouseBuilder.SetRoomNum(3);
HouseBuilder.SetHasGarden(true);
HouseBuilder.SetHasPool(true);
...
HouseBuilder.GetHouse();
或是這樣:
new HouseBuilder().SetDoorNum(2)
.SetWindowNum(4)
.SetRoomNum(3)
.SetHasGarden(true)
.SetHasPool(true)
...
.Build();
以下我們用另一個例子詳細說明,並在 C# 實現這兩種方式。
怎麼實現建造者模式?
開一間水上活動工作室
假設今天你擁有 SUP、獨木舟、浮潛、衝浪……的器材和專業,打算開一個水上活動工作室,設計不同的活動來銷售。
藉由建造者模式,我們可以開始設計不同的課程。
使用建造者模式的方法有兩種:
方式一:透過 Client、Director、Builder 和 Product 形成的建造者模式
Builder: 創建抽象建造者 ITripBuilder
,僅宣告打造水上活動行程的步驟,不實作這些方法
Concrete Builder: 建立實體建造者 KayakTripBuilder
, SupTripBuilder
,實現 ITripBuilder
的各式設定
這邊以 SupTripBuilder
舉例,我們暫且假設 SUP 行程的價格都是 $3,000、行程時間都是 5 小時……,僅有地點是變動的:
Director: 建立 TripDirector
,讓它呼叫 KayakTripBuilder
, SupTripBuilder
以建立行程的各個部分,並宣告一套流程按順序 (從 SetSalesContext, SetDestination, SetPrice… 接下去) 來建造複雜物件
Product: 建立一個物件 Trip
收整行程內容
這邊簡化的用 List 紀錄行程內容的字串,並直接 yield return 這些內容:
Client: 建構完以上物件,我們可以在Program.cs
呼叫 TripDirector
開始打造行程
Console output:
SUP 象鼻岩行程:
【7/1-8/31 SUP 行程 85 折】
地點: 深澳象鼻岩
每人價格: NTD 3000
困難度: 3/5
時間: 5
每團人數限制: 10 人
SUP 活動敘述: 立式槳板運動(英語:Stand Up Paddle, SUP),也俗稱「槳板」, 是起源於夏威夷的一種運動,由衝浪與傳統的手划槳板(Paddleboard)結
合而成。活動器材係由槳板(類似大型衝浪板)加上一支高於身高的單槳所組成。運用於衝浪時又稱立式單槳衝浪(簡稱立槳衝浪),也可在湖泊及河流等水
域,從事探索、激流及救生等多方面的活動。
---
SUP 龜山島行程:
【7/1-8/31 SUP 行程 85 折】
地點: 龜山島牛奶湖
每人價格: NTD 3000
困難度: 3/5
時間: 5
每團人數限制: 10 人
SUP 活動敘述: 立式槳板運動(英語:Stand Up Paddle, SUP),也俗稱「槳板」, 是起源於夏威夷的一種運動,由衝浪與傳統的手划槳板(Paddleboard)結
合而成。活動器材係由槳板(類似大型衝浪板)加上一支高於身高的單槳所組成。運用於衝浪時又稱立式單槳衝浪(簡稱立槳衝浪),也可在湖泊及河流等水
域,從事探索、激流及救生等多方面的活動。
---
獨木舟 東澳行程:
【7/1-8/31 SUP 行程 85 折】
地點: 東澳海蝕洞
每人價格: NTD 3000
困難度: 3/5
時間: 5
每團人數限制: 10 人
SUP 活動敘述: 立式槳板運動(英語:Stand Up Paddle, SUP),也俗稱「槳板」, 是起源於夏威夷的一種運動,由衝浪與傳統的手划槳板(Paddleboard)結
合而成。活動器材係由槳板(類似大型衝浪板)加上一支高於身高的單槳所組成。運用於衝浪時又稱立式單槳衝浪(簡稱立槳衝浪),也可在湖泊及河流等水
域,從事探索、激流及救生等多方面的活動。
---
方式二:透過靜態內部類方式實現建造者模式
上面的假設是 SUP 行程價格、困難度、時間……都相同,僅地點有差異,所以 TripDirector
的參數只有一個。
然而今天可能行程一多,針對同樣都是 SUP(或同樣都是獨木舟)的行程,不同地點我們希望也做不同價格、困難度、時間……的設定。
這時候用第二種實現方法,會更加的彈性。
這種方式使用更加靈活,更符合定義。內部有複雜物件的預設實現,使用時可以根據使用者需求自由定義更改內容,並且無需改變具體的構造方式。就可以生產出不同複雜產品
在我們的例子,就是讓 Trip
本身可以直接設定傳入的參數,並且每個 Builder 方法都回傳 this
,以達到 chaining 的效果。
Builder: 創建抽象建造者 ITripBuilder
,僅宣告打造水上活動行程的步驟,注意回傳型別也都是 ITripBuilder
Concrete Builder: 建立實體建造者 KayakTripBuilder
, SupTripBuilder
,實現 ITripBuilder
的方法
這邊以 SupTripBuilder
舉例,我們把 SetDestination, SetPrice… 等步驟都交給 Trip
這個最後要完工的物件去進行:
Product: 建立一個物件 Trip
收整行程內容
和第一個方法不同,這個方法直接在物件中做 SetDestination, SetPrice… 等步驟。最後一樣全部整理成字串丟到一個 List 裡面:
Client: 建構完以上物件,我們可以在Program.cs
呼叫 Builder 並用 chaining 的方式打造行程
用這個方法可以更彈性就是因為順序(先設定地點還先設定價格?)、屬性多寡(要不要這個行程暫不設定行銷內容跟活動敘述?)都可以直接在建造 Trip 時決定。並且因為 TripBuilder
的方法都是回傳該 TripBuilder
(this),所以可以一路 chain 下去:
Console output:
Trip 1. SUP 象鼻岩行程
【7/1-8/31 SUP 行程 85 折】
地點: 深澳象鼻岩
每人價格: NTD 2000
困難度: 2/5
時間: 4 小時
每團人數限制: 15 人
SUP 活動敘述: 立式槳板運動(英語:Stand Up Paddle, SUP),也俗稱「槳板」, 是起源於夏威夷的一種運動,由衝浪與傳統的手划槳板(Paddleboard)結
合而成。活動器材係由槳板(類似大型衝浪板)加上一支高於身高的單槳所組成。運用於衝浪時又稱立式單槳衝浪(簡稱立槳衝浪),也可在湖泊及河流等水
域,從事探索、激流及救生等多方面的活動。
---
Trip 2. SUP 龜山島行程
【7/1-8/31 SUP 行程 85 折】
地點: 龜山島牛奶湖
每人價格: NTD 5000
困難度: 4/5
時間: 6 小時
每團人數限制: 10 人
SUP 活動敘述: 立式槳板運動(英語:Stand Up Paddle, SUP),也俗稱「槳板」, 是起源於夏威夷的一種運動,由衝浪與傳統的手划槳板(Paddleboard)結
合而成。活動器材係由槳板(類似大型衝浪板)加上一支高於身高的單槳所組成。運用於衝浪時又稱立式單槳衝浪(簡稱立槳衝浪),也可在湖泊及河流等水
域,從事探索、激流及救生等多方面的活動。
---
Trip 3. 獨木舟 小琉球行程
【獨木舟振興券優惠套餐 開跑囉】
地點: 小琉球
每人價格: NTD 3500
困難度: 2/5
時間: 5 小時
每團人數限制: 20 人
SUP 活動敘述: 獨木舟是一種用單根樹幹挖成的划艇,需要藉助槳驅動。獨木舟的優點在於由一根樹幹製成,製作簡單,不易有漏水,散架的風險。它可以說
是人類最古老的水域交通工具之一。
---
Trip 4. 獨木舟 東澳行程
【獨木舟振興券優惠套餐 開跑囉】
地點: 東澳海蝕洞
每人價格: NTD 1500
困難度: 3/5
時間: 4 小時
---
Trip 5. 獨木舟 龍洞行程
【獨木舟振興券優惠套餐 開跑囉】
地點: 龍洞
每人價格: NTD 1500
---
GitHub repo
方式一:透過 Client、Director、Builder 和 Product 形成的建造者模式