如果今天我們想要開一間飲料店,飲料的組合包含了茶、糖,還有牛奶,於是我們可以建立一個 createTea
方法,並依據傳入的參數,來決定最後的產品
class CreateMyTea {
static createTea(tea: boolean, sugar: boolean, milk: boolean): string[] {
const product = []
if (tea) {
product.push('tea')
}
if (milk) {
product.push('milk')
}
if (sugar) {
product.push('sugar')
}
return product
}
}
所以可以得到結果如下
const tea = CreateMyTea.createTea(true, true, false)
tea // ['tea', 'sugar']
const milkTea = CreateMyTea.createTea(true, true, true)
milkTea // ['tea', 'milke', 'sugar']
但這時候就會發現,如果飲料的原料越來越多,那們我們就必須不斷地進入 CreateMyTea
擴充,另一方面,如果在這樣的情況下,我們還想要限制原料「加入的順序」,那麼 CreateMyTea
方法內部邏輯將會非常龐大,而且可能一團糟。
這時候,讓我們來看看一個不一樣的做法
首先,我們將所有加入原料的步驟拆成獨立的抽象方法,並透過一個抽象類別 TeaBuilder
整理起來。在這裡我們還沒有放入實作的細節
abstract class TeaBuilder {
abstract addTea(): void
abstract addMilk(): void
abstract addSugar(): void
}
接下來,可以先來定義最終產品的樣子,這裡我們建立了 Tea
類別
class Tea {
parts: string[] = []
constructor(){}
}
然後,我們可以實作一個自己的 TeaBuilder
,並放入實作的細節
class MyTeaBuilder extends TeaBuilder {
private product: Tea
constructor() {
super()
this.reset()
}
reset(): void {
this.product = new Tea()
}
addTea(): void {
this.product.parts.push('tea')
}
addSugar(): void {
this.product.parts.push('sugar')
}
addMilk(): void {
this.product.parts.push('milk')
}
getProduct(): Tea {
const result = this.product
this.reset()
return result
}
}
有了我們自己的 builder 之後,我們就可以建立一個 director,來負責指揮我們最終的產品該如何被生產出來。在下面的例子當中可以看到,我們有三種建立產品的方法:buildTeaKosong
, buildTea
, buildMilkTea
,當中我們定義了生產該產品需要的「步驟」和「順序」
class Director {
builder: MyTeaBuilder
constructor(){}
setBuilder(builder: MyTeaBuilder): void {
this.builder = builder
}
buildTeaKosong(): Tea {
this.builder.addTea()
return this.builder.getProduct()
}
buildTea(): Tea {
this.builder.addTea()
this.builder.addSugar()
return this.builder.getProduct()
}
buildMilkTea(): Tea {
this.builder.addTea()
this.builder.addSugar()
this.builder.addMilk()
return this.builder.getProduct()
}
}
最後,我們可以呼叫這個 director,來幫助我們生產出我們期待中的產品!
// 建立 director
const myDirector = new Director()
myDirector.setBuilder(new MyTeaBuilder())
const teaKosong = myDirector.buildTeaKosong()
teaKosong.parts // ['tea']
const tea = myDirector.buildTea()
tea.parts // ['tea', 'sugar']
const milkTea = myDirector.buildMilkTea()
milkTea.parts // ['tea', 'sugar', 'milk']
另一方面,我們也可以跳過 director 的指揮,自己操作 builder 製造新產品
myDirector.builder.addTea()
myDirector.builder.addMilk()
const newProduct = myDirector.builder.getProduct()
newProduct.parts // ['tea', 'milk']
建造者模式透過 builder 和 director 的合作,幫助我們管理複雜的生產步驟與順序,同時提供生產不同產品的彈性。建造者模式的優點在於,我們可以彈性的使用各種生產步驟,並且重複使用這些步驟,來面對複雜的需求。
在上面的例子當中,builder 負責管理各個生產步驟的實作,而 director 管理步驟的組合與順序。
所以對使用者來說,我們不需要知道實際生產的實作方式、順序為何,只要呼叫對的 director 和其方法,就能夠得到我們想要的產品,譬如 milkTeak。
工廠模式專注在產出的結果,而建造者模式較多關注在生產的步驟、組合和順序上面。另一方面,工廠模式產出的產品為同一(或類似)性質的產品,但透過建造者模式,我們可以創造出非常多變化的產品