【设计模式】3、builder 建造者模式

三、builder 模式(生成器)

https://refactoringguru.cn/design-patterns/builder

# 需求
1. 如果需要构造的产品, 可能有很多种类, 则构造函数需要很多参数.
但如果实际每种产品只需要部分参数, 那么就会出现冗长的参数列表, 难以使用.

# 方案
builder 模式, 把产品的构造代码, 单独放在一个名为 builder 的独立对象中.

该模式, 将对象的构建, 拆分为若干步骤.

每次创建对象时, 都通过 builder 对象执行其中的部分步骤.

## builder 是接口
如果希望生成多种类似的产品, 则希望 builder 是接口

## 可选的 director(主管)
client 可以直接使用 builder, 也可以借助 director (让 director 封装 builder 的各种快捷操作)

目录层级如下:

03builder
├── 031house
│   ├── builder.go
│   ├── director.go
│   ├── director_test.go
│   ├── house.go
│   ├── igloo_builder.go
│   ├── normal_builder.go
│   └── readme.md
└── readme.md

3.1 build 房屋

# 示例
参考: https://refactoringguru.cn/design-patterns/builder/go/example
需求: build 各种房屋: 如 冰屋, 或普通房屋

1. 抽象 build 过程中的相似步骤: 因为各种房屋的 build 流程是相似的, 则可以抽象出 [相似的步骤].
2. 定义 builder 接口: 把这些 [相似的步骤] 放在 builder 接口中.
3. 实现 builder 接口: 然后, [builder 接口] 可以有多种实现, 比如 [冰屋 builder 实现], 或 [普通房屋 builder 实现].
4. builder 提供获取产品的方法: 因为 [各种 builder 的实现] 构建出的产品, 并不一定是符合相同接口的, 所以, 每种 builder 可以获取自己的构建的产品结果.
5. (可选) 定义 director, 封装 builder 的常见操作: 因为 builder 的步骤很多, 为了方便 client 的使用, 可以把常见操作封装在 director 中. (即把复杂封装在 director, 把简单留给 client)
6. client 使用: client 可以使用 director, 或者使用 builder, 构建产品, 并获取产品.

具体详见本文件夹的代码

3.1.1 builder.go

package _31builder

type builder interface {
	// SetWindowType 步骤: 设置窗户
	SetWindowType()
	// SetDoorType 步骤: 设置门
	SetDoorType()
	// SetNumFloor 步骤: 设置楼层
	SetNumFloor()
	// GetHouse 结果: 获取建造出的房屋
	GetHouse() house
	// Reset 清空构造器的所有步骤, 以备后续建造
	Reset()
}

3.1.2 director.go

package _31builder

// director 主管, 封装了常见的 builder 流程, 方便 client 使用
type director struct {
	// builder 封装的建造者
	builder builder
}

// NewDirector 新建主管
func NewDirector(b builder) *director {
	return &director{
		builder: b,
	}
}

// SetBuilder 给 Director 切换 builder
func (d *director) SetBuilder(b builder) {
	d.builder = b
}

// BuildHouse 真正封装的行为: 使用 builder 建造房屋
func (d *director) BuildHouse() house {
	b := d.builder
	// 执行 builder 的各步骤
	b.SetWindowType()
	b.SetDoorType()
	b.SetNumFloor()
	// 获取 build 的结果
	hs := b.GetHouse()
	// 清空已执行的步骤, 以备后续 build 新产品
	b.Reset()
	return hs
}

3.1.3 director_test.go

package _31builder

import (
	"github.com/stretchr/testify/require"
	"testing"
)

// 使用 director 构建房屋
func TestDirectorBuildHouse(t *testing.T) {
	iglooBuilder := NewIglooBuilder()
	director := NewDirector(iglooBuilder)
	iglooHouse := director.BuildHouse()
	require.EqualValues(t, "冰窗", iglooHouse.windowType)
	require.EqualValues(t, "冰门", iglooHouse.doorType)
	require.EqualValues(t, 1, iglooHouse.numFloor)

	normalBuilder := NewNormalBuilder()
	director.SetBuilder(normalBuilder)
	normalHouse := director.BuildHouse()
	require.EqualValues(t, "落地窗", normalHouse.windowType)
	require.EqualValues(t, "防盗门", normalHouse.doorType)
	require.EqualValues(t, 10, normalHouse.numFloor)
}

3.1.4 house.go

package _31builder

// 建造出的房屋产品
type house struct {
	windowType string
	doorType   string
	numFloor   int
}

3.1.5 igloo_builder.go

package _31builder

// iglooBuilder 冰屋建造者
type iglooBuilder struct {
	windowType string
	doorType   string
	numFloor   int
	// 建造的结果
	house house
}

// NewIglooBuilder 构造 iglooBuilder, 返回接口, 以便调用者(如 director)切换不同的 builder 实现
func NewIglooBuilder() builder {
	return &iglooBuilder{
		windowType: "",
		doorType:   "",
		numFloor:   0,
		house:      house{},
	}
}

func (b *iglooBuilder) SetWindowType() {
	b.windowType = "冰窗"
}

func (b *iglooBuilder) SetDoorType() {
	b.doorType = "冰门"
}

func (b *iglooBuilder) SetNumFloor() {
	b.numFloor = 1
}

func (b *iglooBuilder) GetHouse() house {
	return house{
		windowType: b.windowType,
		doorType:   b.doorType,
		numFloor:   b.numFloor,
	}
}

func (b *iglooBuilder) Reset() {
	b.windowType = ""
	b.doorType = ""
	b.numFloor = 0
	b.house = house{}
}

3.1.6 normal_builder.go

package _31builder

// normalBuilder 普通房屋建造者
type normalBuilder struct {
	windowType string
	doorType   string
	numFloor   int
	// 建造的结果
	house house
}

// NewNormalBuilder 构造 normalBuilder, 返回 builder 接口, 方便调用者切换不同的 builder 实现
func NewNormalBuilder() builder {
	return &normalBuilder{
		windowType: "",
		doorType:   "",
		numFloor:   0,
		house:      house{},
	}
}

func (b *normalBuilder) SetWindowType() {
	b.windowType = "落地窗"
}

func (b *normalBuilder) SetDoorType() {
	b.doorType = "防盗门"
}

func (b *normalBuilder) SetNumFloor() {
	b.numFloor = 10
}

func (b *normalBuilder) GetHouse() house {
	return house{
		windowType: b.windowType,
		doorType:   b.doorType,
		numFloor:   b.numFloor,
	}
}

func (b *normalBuilder) Reset() {
	b.windowType = ""
	b.doorType = ""
	b.numFloor = 0
	b.house = house{}
}
3.1.7 测试
go test -v ./03builder/...
=== RUN   TestDirectorBuildHouse
--- PASS: TestDirectorBuildHouse (0.00s)
PASS
ok      godp/03builder/031house 0.111s

3.2 option

需求: 实现连接池, 其有若干参数, 如最大连接数, 最大空闲连接数, 是否打印 sql 等

如果希望用户可以传入可选的参数, 覆盖默认的参数, 则可以用 builder 模式, 实现链式的调用

目录层级:

03builder/032conn_pool
├── option.go
├── pool.go
├── pool_test.go
└── readme.md

3.2.1 pool_test.go

package _32conn_pool

import (
	"github.com/stretchr/testify/require"
	"testing"
)

// 直接方式, 需 client 手动创建 option
func TestNewPoolDirectly(t *testing.T) {
	// 支持默认行为
	pDefault := NewPool()
	require.EqualValues(t, defaultMaxConn, pDefault.MaxConn)
	require.EqualValues(t, defaultMaxIdleConn, pDefault.MaxIdleConn)
	require.EqualValues(t, defaultShowSQL, pDefault.ShowSQL)

	// 支持自定义行为, 初始化时调用
	expectMaxConn := 8
	expectMaxIdleConn := 6
	expectShowSQL := true
	opts := []poolOpt{
		func(p *pool) { p.MaxConn = expectMaxConn },
		func(p *pool) { p.MaxIdleConn = expectMaxIdleConn },
		func(p *pool) { p.ShowSQL = expectShowSQL },
	}
	p := NewPool(opts...)
	require.EqualValues(t, expectMaxConn, p.MaxConn)
	require.EqualValues(t, expectMaxIdleConn, p.MaxIdleConn)
	require.EqualValues(t, expectShowSQL, p.ShowSQL)

	// 支持自定义行为, 初始化时调用, 且后续继续调用
	expectMaxConn = 7
	expectMaxIdleConn = 4
	expectShowSQL = true
	p1 := NewPool(func(p *pool) { p.MaxConn = expectMaxConn }, func(p *pool) { p.MaxIdleConn = expectMaxIdleConn }).WithOpts(
		func(p *pool) { p.MaxIdleConn = expectMaxIdleConn },
		func(p *pool) { p.ShowSQL = expectShowSQL },
	)
	require.EqualValues(t, expectMaxConn, p1.MaxConn)
	require.EqualValues(t, expectMaxIdleConn, p1.MaxIdleConn)
	require.EqualValues(t, expectShowSQL, p1.ShowSQL)
}

// 简易方式, 封装常见步骤, 并链式调用各步骤
func TestNewPoolEasy(t *testing.T) {
	// 支持默认行为
	pDefault := NewPool()
	require.EqualValues(t, defaultMaxConn, pDefault.MaxConn)
	require.EqualValues(t, defaultMaxIdleConn, pDefault.MaxIdleConn)
	require.EqualValues(t, defaultShowSQL, pDefault.ShowSQL)

	// 支持自定义行为
	p := NewPool().setMaxConn(8).setMaxIdleConn(6).setShowSQL(true)
	require.EqualValues(t, 8, p.MaxConn)
	require.EqualValues(t, 6, p.MaxIdleConn)
	require.EqualValues(t, true, p.ShowSQL)
}

3.3.2 pool.go

package _32conn_pool

// 定义 产品, 即连接池 pool
type pool struct {
	// 最大连接数
	MaxConn int
	// 最大空闲连接
	MaxIdleConn int
	// 打印 SQL
	ShowSQL bool
}

const (
	defaultMaxConn     = 10
	defaultMaxIdleConn = 5
	defaultShowSQL     = false
)

// NewPool 构造 pool
// 因为 opts 是可选的参数(即 ... 的), 如果未传入则期望默认行为, 如果传入则期望覆盖部分默认行为
func NewPool(opts ...poolOpt) *pool {
	// 默认连接池
	p := &pool{
		MaxConn:     defaultMaxConn,
		MaxIdleConn: defaultMaxIdleConn,
		ShowSQL:     defaultShowSQL,
	}

	// 应用传入的选项, 覆盖默认的参数
	for _, opt := range opts {
		opt(p)
	}

	// 返回构造的连接池
	return p
}

func (p *pool) WithOpts(opts ...poolOpt) *pool {
	for _, opt := range opts {
		opt(p)
	}
	return p
}

// 封装常见步骤
func (p *pool) setMaxConn(n int) *pool {
	p.MaxConn = n
	return p
}

func (p *pool) setMaxIdleConn(n int) *pool {
	p.MaxIdleConn = n
	return p
}

func (p *pool) setShowSQL(s bool) *pool {
	p.ShowSQL = s
	return p
}

3.3.3 option.go

package _32conn_pool

// 定义 option 函数, 其会被传入 产品的构造函数, 用于改变产品的默认参数
// 该函数的输入是 pool 指针
// 函数内部, 会改变 pool 指针的某些参数
// 该函数没有输出值
type poolOpt func(p *pool)

3.3 自行车加工

https://kamacoder.com/problempage.php?pid=1084

题目描述
小明家新开了一家自行车工厂,用于使用自行车配件(车架 frame 和车轮 tires )进行组装定制不同的自行车,包括山地车和公路车。

山地车使用的是Aluminum Frame(铝制车架)和 Knobby Tires(可抓地轮胎),公路车使用的是 Carbon Frame (碳车架)和 Slim Tries。

现在它收到了一笔订单,要求定制一批自行车,请你使用【建造者模式】告诉小明这笔订单需要使用那些自行车配置吧。


输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 100),表示订单的数量。 

接下来的 N 行,每行输入一个字符串,字符串表示客户的自行车需求。

字符串可以包含关键词 "mountain""road",表示客户需要山地自行车或公路自行车。

输出描述
对于每笔订单,输出该订单定制的自行车配置。
输入示例
3
mountain
road
mountain

输出示例
Aluminum Frame Knobby Tires
Carbon Frame Slim Tires
Aluminum Frame Knobby Tires

提示信息
在本例中:产品为自行车,可以有两个建造者:山地车建造者和公路车建造者。

3.3.1 目录层级

├── bicycle.go
├── bicycle_builder.go
├── director.go
├── main.go
├── mountain_bicycle_builder.go
├── readme.md
└── road_bicycle_builder.go

3.3.2 bicycle_builder

package main

// BicycleBuilder 自行车建造者, 通用接口
type BicycleBuilder interface {
	// 构建车架
	buildFrame()
	// 构建轮胎
	buildTrie()
	// 获取构建的结果
	getBicycle() bicycle
	// 重置
	reset()
}

3.3.3 mountain_bicycle_builder

package main

// 山地车建造者, 具体实现
type mountainBicycleBuilder struct {
	// 构建出的产品: 山地车
	mountainBicycle bicycle
}

func NewMountainBicycleBuilder() *mountainBicycleBuilder {
	return &mountainBicycleBuilder{}
}

// 山地车建造者, 建造车架实现
func (b *mountainBicycleBuilder) buildFrame() {
	b.mountainBicycle.frame = "Aluminum Frame"
}

// 山地车建造者, 建造轮胎实现
func (b *mountainBicycleBuilder) buildTrie() {
	b.mountainBicycle.trie = "Knobby Tires"
}

func (b *mountainBicycleBuilder) getBicycle() bicycle {
	return b.mountainBicycle
}

func (b *mountainBicycleBuilder) reset() {
	b.mountainBicycle = bicycle{}
}

3.3.4 road_bicycle_builder

package main

// 公路车建造者, 具体实现
type roadBicycleBuilder struct {
	// 构建出的产品: 公路车
	roadBicycle bicycle
}

func NewRoadBicycleBuilder() *roadBicycleBuilder {
	return &roadBicycleBuilder{}
}

// 公路车建造者, 建造车架, 具体实现
func (b *roadBicycleBuilder) buildFrame() {
	b.roadBicycle.frame = "Carbon Frame"
}

// 公路车建造者, 建造轮胎, 具体实现
func (b *roadBicycleBuilder) buildTrie() {
	b.roadBicycle.trie = "Slim Tires"
}

// 获取构建的产品: 公路车
func (b *roadBicycleBuilder) getBicycle() bicycle {
	return b.roadBicycle
}

func (b *roadBicycleBuilder) reset() {
	b.roadBicycle = bicycle{}
}

3.3.5 director

package main

type Director struct {
	builder BicycleBuilder
}

func NewDirector() *Director {
	return &Director{builder: nil}
}

func (d *Director) BuildBicycle() bicycle {
	d.builder.buildFrame()
	d.builder.buildTrie()
	bi := d.builder.getBicycle()
	d.builder.reset()
	return bi
}

func (d *Director) setBuilder(b BicycleBuilder) {
	d.builder = b
}

3.3.6 bicycle

package main

import "fmt"

// 公路车, 产品
type bicycle struct {
	// 车架
	frame string
	// 轮胎
	trie string
}

// 打印 公路车的信息
func (r *bicycle) info() string {
	return fmt.Sprintf("%v %v", r.frame, r.trie)
}

3.3.7 main

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

/*
// 运行
go run .

// 输入
3
mountain
road
mountain

// 输出
Aluminum Frame Knobby Tires
Carbon Frame Slim Tires
Aluminum Frame Knobby Tires
*/
func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()
	n, _ := strconv.Atoi(scanner.Text())

	for i := 0; i < n; i++ {
		scanner.Scan()
		bicycleType := scanner.Text()

		var builder BicycleBuilder
		switch bicycleType {
		case "mountain":
			builder = NewMountainBicycleBuilder()
		case "road":
			builder = NewRoadBicycleBuilder()
		default:
			continue
		}
		d := NewDirector()
		d.setBuilder(builder)
		bi := d.BuildBicycle()
		fmt.Println(bi.info())
	}
}
  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

呆呆的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值