Go-Design-Patterns-学习笔记 3 建造者模式

对建造者模式,有些不懂呢,和工厂模式有啥区别呢?争取这篇写完,能清晰一些。

正文

Chapter 2: Creational Patterns - Singleton, Builder, Factory, Prototype, and Abstract Factory Design Pattern

建造者模式 Builder design pattern

描述 Description

创建一个实例,需要的步骤可以很简单,也可以非常复杂。有时候,你需要相同的技术和步骤去创建不同类型的对象。比如,你用几乎相同的技术去建造一辆小轿车或者大客车,除了他们的尺寸以及作为数不太相同外。我们为什么不复用建造过程呢? So, 建造者模式出现了。

目标 Objectives

建造者设计模式试图达成如下目标:

  • 对复杂创建过程进行抽象,让对象的创建与对象使用者进行分离
  • 通过填满对象的字段和内部包含的子对象,一步一步地创建完整对象
  • 针对多个对象的创建,复用创建算法

例子 交通工具的制造 example - vehicle manufacturing

建造者模式一般被描述为一个指挥者,建造过程,一些建造者,以及建造者所生产的产品之间的关系。在我们的例子中,创建一个交通工具(产品),大致有这么些过程:

  • 原则交通工具类别
  • 组装结构主体
  • 装上轮子
  • 安装座位

不失一般性,我们这里举例要建造小轿车和摩托车

要求和验收标准 Requirements and acceptance criteria

如之前描述,我们需要创建小轿车、摩托车的建造者、一个总指挥 ManufacturingDirector。相关的要去如下:

  • 我们需要一个制造类型,负责车辆的所有结构
  • 一个 car 建造者,建造出的车辆产品是四个轮子,五个座位
  • 一个 motorbike 建造者,建造出的车辆产品是两个轮子,两个座位
  • BuildProcess 创建的 VehicleProduct 必须对修改开放

单元测试

新建一个文件 builder,创建 builder.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package builder

type VehicleProduct struct {
Wheels int
Seats int
Structure string
}

type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}

type ManufacturingDirector struct {

}

func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
// Implementation goes here
}

func (f *ManufacturingDirector) Construct() {
// Implementation goes here
}

type CarBuilder struct {}

func (c *CarBuilder) SetWheels() BuildProcess {
return nil
}

func (c *CarBuilder) SetSeats() BuildProcess {
return nil
}

func (c *CarBuilder) SetStructure() BuildProcess {
return nil
}

func (c *CarBuilder) GetVehicle() VehicleProduct {
return VehicleProduct{}
}

type BikeBuilder struct {}

func (b *BikeBuilder) SetWheels() BuildProcess {
return nil
}

func (b *BikeBuilder) SetSeats() BuildProcess {
return nil
}

func (b *BikeBuilder) SetStructure() BuildProcess {
return nil
}

func (b *BikeBuilder) GetVehicle() VehicleProduct {
return VehicleProduct{}
}

新建 builder_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package builder

import "testing"

func TestBuilderPattern(t *testing.T) {
manufacturingComplex := ManufacturingDirector{}

carBuilder := &CarBuilder{}
manufacturingComplex.SetBuilder(carBuilder)
manufacturingComplex.Construct()

car := carBuilder.GetVehicle()
expectedWheels := 4

if car.Wheels != expectedWheels {
t.Errorf("Wheels on a car must be 4 but we get %d \n", car.Wheels)
}

expectedStructure := "Car"
if car.Structure != expectedStructure {
t.Errorf("Structure on a car must be 'Car' but was %s \n ", car.Structure)
}

expectedSeats := 5
if car.Seats != expectedSeats {
t.Errorf("Seats on a car must be 5 but they were %d \n", car.Seats)
}

bikeBuilder := &BikeBuilder{}
manufacturingComplex.SetBuilder(bikeBuilder)
manufacturingComplex.Construct()

motorbike := bikeBuilder.GetVehicle()

expectedWheels = 2
if motorbike.Wheels != expectedWheels {
t.Errorf("wheels on a motorbike must be 2 but the were %d \n", motorbike.Wheels)
}

expectedStructure = "Motorbike"
if motorbike.Structure != expectedStructure {
t.Errorf("Sturcture on a motorbike must be 'Motorbike' and was %s \n", motorbike.Structure)
}

expectedSeats = 2
if motorbike.Seats != expectedSeats {
t.Errorf("seats on a motorbike must be 2 but wa %d \n", motorbike.Seats)
}
}

实现 Implementation

补齐 builder.go 中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package builder

type VehicleProduct struct {
Wheels int
Seats int
Structure string
}

type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}

type ManufacturingDirector struct {
builder BuildProcess
}

func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
// Implementation goes here
f.builder = b
}

func (f *ManufacturingDirector) Construct() {
// Implementation goes here
f.builder.SetSeats().SetStructure().SetWheels()
}

type CarBuilder struct {
v VehicleProduct
}

func (c *CarBuilder) SetWheels() BuildProcess {
c.v.Wheels = 4
return c
}

func (c *CarBuilder) SetSeats() BuildProcess {
c.v.Seats = 5
return c
}

func (c *CarBuilder) SetStructure() BuildProcess {
c.v.Structure = "Car"
return c
}

func (c *CarBuilder) GetVehicle() VehicleProduct {
return c.v
}

type BikeBuilder struct {
v VehicleProduct
}

func (b *BikeBuilder) SetWheels() BuildProcess {
b.v.Wheels = 2
return b
}

func (b *BikeBuilder) SetSeats() BuildProcess {
b.v.Seats = 2
return b
}

func (b *BikeBuilder) SetStructure() BuildProcess {
b.v.Structure = "Motorbike"
return b
}

func (b *BikeBuilder) GetVehicle() VehicleProduct {
return b.v
}

运行 go test -cover 测试通过。
我们现在需要拓展下代码功能,比如希望加一个大客车建造者,先写必要的结构体名及函数。
在 builder.go 增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type BusBuilder struct {
v VehicleProduct
}

func (b *BusBuilder) SetStructure() BuildProcess {
return nil
}

func (b *BusBuilder) SetWheels() BuildProcess {
return nil
}

func (b *BusBuilder) SetSeats() BuildProcess {
return nil
}

func (b *BusBuilder) GetVehicle() VehicleProduct {
return VehicleProduct{}
}

在 builder_test.go,增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
busBuilder := &BusBuilder{}
manufacturingComplex.SetBuilder(busBuilder)
manufacturingComplex.Construct()

bus := busBuilder.GetVehicle()

expectedStructure = "Bus"
if bus.Structure != expectedStructure {
t.Errorf("Structure of the bus should be 'Bus', but got %s \n", bus.Structure)
}

expectedWheels = 4*2
if bus.Wheels != expectedWheels {
t.Errorf("Wheels on bus should be %d, but got %d \n", expectedWheels, bus.Wheels)
}

expectedSeats = 30
if bus.Seats != expectedSeats {
t.Errorf("Seats on bus should be %d, but got %d \n", expectedSeats, bus.Seats)
}

运行 go test -cover,测试不通过,填补好代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type BusBuilder struct {
v VehicleProduct
}

func (b *BusBuilder) SetStructure() BuildProcess {
b.v.Structure = "bus"
return b
}

func (b *BusBuilder) SetWheels() BuildProcess {
b.v.Wheels = 4*2
return b
}

func (b *BusBuilder) SetSeats() BuildProcess {
b.v.Seats = 30
return b
}

func (b *BusBuilder) GetVehicle() VehicleProduct {
return b.v
}

运行 go test -cover,测试通过。

小结

  • Director struct 用来指定 builder 并且发出建造指令
  • BuilderProcess interface 用来对建造过程进行抽象,类比 python 中的抽象类的抽象方法
  • Builder struct 建造者,被 Director 调用,并实现 BuilderProcess 的全部方法
  • Product struct 产品,被 Builder 建造

建造者模式让我们通过 director 以一种通用的构造算法去把控位置数量的对象的创建。

同时,对建造模式的定义,让添加新的产品很是便利。未来 builders 必须实现 BuilderProcess 接口。

当 BuilderProcess 不是很稳定的时候,要避免使用建造者模式,因为轻微的改动会影响到所有的建造者。并且会变得奇怪的是,你为一些建造者添加了一个新方法,但是另外一些建造者却没有这个方法

总结

算是又有些进步了,但是对 Go 接口的理解还是不是很深刻,期待后期会加强。