DAY 17:Builder Pattern,一步一步的建造产品

什麽是 Builder Pattern?

将建造物件的实作拆开,由使用者觉得要选择建造什麽,来一步一步建造

举例来说,当创建 PS5 时,

CreatePS5(hasCPU, hasGPU bool)

如果有许多额外的选项,

CreatePS5(CPUCores uint8, has大摇, has蓝牙耳机 bool)

就会发现额外的选项需透过零值(zero value)来解决,

CreatePS5(4, false, true)

这使得建构 PS5 的 function 难以快速理解其中的意义。

所以需要额外将这些步骤拆除,再由使用者决定是否要执行,并且将这些步骤拆分,也使得建构 function 不至於过长,

CreatePS5(4).
Set大摇(true).
// Set蓝牙耳机(true) 不执行就不会有蓝牙耳机

优点:

  • 将必要的建构参数放在 create function,额外的参数放在 set function,能在程序弹性时也拥有可读性
  • 不使建构 function 过於复杂

设计上通常有ProductDirectorBuilder InterfaceConcreteBuilder

  • Product: 实际产品
  • Director: 操作 Builder 来生产产品的物件
  • Builder Interface: 生产产品的物件的 interface,由於有此 interface,可以让 Director 使用不同 ConcreteBuilder 来生产物件
  • ConcreteBuilder: 实际生产产品的物件

问题情境

使用者购买 PS5,并且其中有多个周边可以购买,使用者自行决定要加购什麽周边

解决方式

UML 图後如下:

程序码如下:

(相关的 code 在Github - go-design-patterns)

package main

import (
	"fmt"
	"strings"
)

type PS5Builder interface {
	SetController(isBuy bool) PS5Builder
	SetBluetoothHeadphones(isBuy bool) PS5Builder
	Build() *PS5
}

type PS5 struct {
	cpu                 string
	gpu                 string
	controller          string
	bluetoothHeadphones string
}

func CreatePS5() *PS5 {
	return &PS5{
		cpu: "cpu",
		gpu: "gpu",
	}
}

func (p PS5) PlayGame() {
	var (
		accessories     []string
		withAccessories string
	)
	if p.controller != "" {
		accessories = append(accessories, p.controller)
	}
	if p.bluetoothHeadphones != "" {
		accessories = append(accessories, p.bluetoothHeadphones)
	}
	if len(accessories) != 0 {
		withAccessories = " with " + strings.Join(accessories, ", ")
	}
	fmt.Printf("loading...play%s!\n", withAccessories)
}

type PS5Director struct {
	Builder PS5Builder
}

func CreatePS5Director(concretePS5Builder *ConcretePS5Builder) *PS5Director {
	return &PS5Director{
		Builder: concretePS5Builder,
	}
}

func (p PS5Director) Construct() *PS5 {
	return p.Builder.
		SetController(true).
		Build()
}

type ConcretePS5Builder struct {
	ps5 *PS5
}

func CreateConcretePS5Builder() *ConcretePS5Builder {
	return &ConcretePS5Builder{
		ps5: CreatePS5(),
	}
}

func (p *ConcretePS5Builder) SetController(isBuy bool) PS5Builder {
	if isBuy {
		p.ps5.controller = "FightingStick"
	}
	return p
}

func (p *ConcretePS5Builder) SetBluetoothHeadphones(isBuy bool) PS5Builder {
	if isBuy {
		p.ps5.bluetoothHeadphones = "BluetoothHeadphones"
	}
	return p
}

func (p *ConcretePS5Builder) Build() *PS5 {
	return p.ps5
}

func main() {
	concretePS5Builder := CreateConcretePS5Builder()
	ps5Director := CreatePS5Director(concretePS5Builder)
	ps5 := ps5Director.Construct()

	ps5.PlayGame()
}

CreateConcretePS5Builder()建立实际的 builder 後,丢入PS5Director{},而PS5Director{}会在透过Construct()来操作 builder 去 set ps5。

可以发现必填的栏位在CreateConcretePS5Builder()就已经填好,如果是有建构子(construct)的语言,例如 java 就会在建构子内做好,由於 golang 没有,所以会用 create function 来达到此效果。而额外的栏位即在.SetXXX()function 设置。

另外在.SetXXX()function 後又会 return 自己的方式称为 Fluent interface,此方法可以做出链式的写法,即.SetXXX().SetXXX(),可以让 set function 的使用更灵活。


<<:  [Day22] Emmet 学习笔记 - CSS篇

>>:  【领域展开 15 式】 裸窥 WordPress Soledad 主题客制栏位

什麽是架构(What Is Architecture)?

-计算机架构 作为解决方案最重要的工件,架构是一个对象(解决方案)从各种观点或角度的概念、逻辑和物...

【Day 28】练习专案 1/2 - NFC、Barcode、fontFamily、Stack、sliding_up_panel

今日要点 》前言 》介绍 Demo 专案 》程序架构研究 前言 前面精选了很多在 Github 上很...

[Day23] 在 Codecademy 学 React ~ Component Lifecycle 生命周期我不懂你QQ

前言 原本以为生命周期应该很好懂, 但我卡在别的地方, 不过快 12 点了啊我先 po 出我目前进度...

LineBot - 申请

可以用 shioaji api 取得即时报价後,就有很多应用场景,这边先示范一个比较简单的,使用 L...

Day 26 - 使用者影音互动 - 即时串流声音与影片

目标 学习如何抓取跟分析声音制作互动 了解如何抓取与绘制使用者的即时影片 学习如何把即时影像转成即时...