Builder 建造者模式

如果今天我们想要开一间饮料店,饮料的组合包含了茶、糖,还有牛奶,於是我们可以建立一个 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。

跟工厂模式相比

工厂模式专注在产出的结果,而建造者模式较多关注在生产的步骤、组合和顺序上面。另一方面,工厂模式产出的产品为同一(或类似)性质的产品,但透过建造者模式,我们可以创造出非常多变化的产品


<<:  Day21-React 简易动画篇-下篇

>>:  Day 20 - 装个 Nessus 试试

Android Studio - 心得

经过这三十天的每天发文 每天督促自己学习新的东西并记录下来 没想到已经坚持到最後一天了!! 虽然其实...

第六章

大家在玩CMS之前应该都有先在本地端做测试的习惯吧,那应该会有遇到那种像是使用了XAMPP在本地端架...

[DAY22] Boxenn Use Case Spec

Use Case Spec 这边以之前的 use case 当作例子来撰写测试。 首先要能快速地建立...

(Day15) 闭包进阶使用,工厂模式及私有方法。

上回介绍闭包概念以及闭包大致运用,这次则介绍实做比较常用闭包的几种模式 工厂模式 上个章节有介绍到,...

【领域展开 10 式】真的需要看教学文,正式启用布景主题 Soledad 与网站做绑定

购买後需详阅公开声明书 会下这标题的原因是,主题真正购买後的安装设定,真的需要参考网路文章才能少走很...