iT邦幫忙

2021 iThome 鐵人賽

DAY 20
1

如果今天我們想要開一間飲料店,飲料的組合包含了茶、糖,還有牛奶,於是我們可以建立一個 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。

跟工廠模式相比

工廠模式專注在產出的結果,而建造者模式較多關注在生產的步驟、組合和順序上面。另一方面,工廠模式產出的產品為同一(或類似)性質的產品,但透過建造者模式,我們可以創造出非常多變化的產品


上一篇
Abstract Factory 抽象工廠模式
下一篇
Prototype 原型模式
系列文
幫自己搞懂物件導向和設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言