从“汽车制造”生活案例到软件的建造者模式

构建者模式帮助我们构建复杂的对象,而不需要直接实例化它们的结构,或编写它们所需的逻辑。想象一下,一个对象可能有几十个字段,它们本身就是比较复杂的结构。

构建者模式帮助我们构建复杂的对象,而不需要直接实例化它们的结构,或编写它们所需的逻辑。想象一下,一个对象可能有几十个字段,它们本身就是比较复杂的结构。

从“汽车制造”生活案例到软件的建造者模式

​想了解更多关于开源的内容,请访问:​

​清一色开源基础软件社区​

​https://ost.51cto.com​

一、生活案例

沙师弟 : “大师兄,车是怎么建成的啊?”

大师兄:“从外部看,车由车身、座椅和轮胎,从内部又有引擎、方向盘、电路系统、刹车系统、冷却系统等等组成,这些复杂的部件一般都不是一个厂商来完成的,而是将这些交付给汽车零部件制造商。不同的生产商来最终完成不同部件的生产,采购完整个零部件,最后在车间完成整个组装。”

汽车这个复杂的对象就可以通过建造者模式来将部件和组装过程分开,帮我们快速完成汽车的建造。

二、建造者模式

构建者模式帮助我们构建复杂的对象,而不需要直接实例化它们的结构,或编写它们所需的逻辑。想象一下,一个对象可能有几十个字段,它们本身就是比较复杂的结构。

现在,你有许多具有这些特征的对象,还可以有更多。我们不希望在包中写创建所有这些对象的逻辑,而只在需要使用这些对象的地方写好。

1、Go 中的对象实例

Go 语言中,实例的创建可以很简单,比如只是简单提供​​{}​​,然后让实例的值为零;也可以很复杂,比如一个对象需要进行一些 API 调用,检查状态,并为其字段创建对象。

你也可以有一个由多个对象组成的对象,这在 Go 中是非常常见的,因为 Go 不支持继承。

同时,你可以用同样的技术来创造许多类型的对象。例如,你将使用几乎相同的技术来建造一辆汽车和一辆公共汽车,只是它们的尺寸和座位数不同,所以我们为什么不重复使用建造过程呢?这就是建造者模式的用武之地了。

2、建造者模式的优点

  • 对复杂的创建进行抽象,以便将对象与对象的使用者进行分开
  • 通过填入字段和创建嵌入对象,一步步创建对象
  • 能够在许多对象之间重复使用对象创建方法

3、交通工具制造的例子

建造者模式通常被描述为一个主管 director、几个 Builder 和他们所创建的产品之间的关系。

我们来看关于汽车的例子,创建一个车辆建造器Builder,建造车(Product)的过程或许会有一些差异,但对每一种车辆来说,整体的过程可以归纳为如下步骤:

  1. 选择车辆类型
  2. 组装结构
  3. 安装车轮
  4. 放置座椅

如果你仔细思考,你可以重复通过这个步骤描述建造一辆骑车和一辆摩托车,在接下来的例子中,主管 director 的角色就是用Manufacturing变量进行表示。

4、设计思路

正如上述的描述那样,我们必须处理一些Builder变量和一个独立的director. 主管director来领导实际建造者Builder的建造产品的过程。因此,对于一个车辆建造者的要求是:

  • 需要有一个制造对象来制造交通工具的一切
  • 当使用汽车建造者Builder时,返回的车辆产品必须带有 4 个轮子、5 个座椅和定义为Car的结构体
  • 使用摩托车建造者时,返回的车辆产品必须带有 2 个轮子、供 2 个人的座位和定义为Motorbike的结构体
  • 任何BuilderProcess建造者必须开放对车辆产品的修改功能

结构图如下:

#打卡不停更# 从“汽车制造”生活案例到软件的建造者模式-开源基础软件社区

三、测试驱动开发

根据前文的设计过程,我们将建造一个 director 变量:ManufacturingDirector, 以使用由汽车和摩托车产品建造器的建造过程。dierctor是负责人,builder是实际的建造者。

1、Builder 接口声明

Builder声明如下:

package creational

type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
  • BuildProcess接口定义了建造车辆所需的步骤,因此,各个车辆的Builder必须实现这个接口。
  • 在每个SetXXX()的函数,返回每一个构建的过程,然后将各个步骤连接起来,返回一个GetVehicle()的方法。

2、Director 主管接口

ManufacturingDirector主管接口可以来自接收不同的Builder:

  • 然后有一个Construct()方法使用Builder来重复建造过程,后面会实现这个方法。
  • SetBuilder()方法用于更换不同的Builder。
// 制作主管
type ManufacturingDirector struct{}
func (f *ManufacturingDirector) Construct() {// 建筑过程
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {// 选择建造者
}

3、Product 产品结构体

产品是我们在制造出的最终对象。在上面的简易例子中,我们假设一辆交通工具是由车轮、座椅和结构组成的。

// 产品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}

4、Builder 具体建造者

第一个 Builder 为 Car 建造者Builder,需要我们实现定义在BuildProcess接口的方法:

// 汽车建造者
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 MotorBuilder struct{}

func (m *MotorBuilder) SetWheels() BuildProcess {return nil
}
func (m *MotorBuilder) SetSeats() BuildProcess {return nil
}
func (m *MotorBuilder) SetStructure() BuildProcess {return nil
}
func (m *MotorBuilder) GetVehicle() VehicleProduct {return VehicleProduct{}
}

最终,我们得到完整的creational.go文件:

package creational

// 建造过程
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
// 制作主管
type ManufacturingDirector struct{}
func (f *ManufacturingDirector) Construct() {
// 等待实现
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
// 等待实现
}
// 产品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
// 汽车建造者
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 MotorBuilder struct{}
func (m *MotorBuilder) SetWheels() BuildProcess {
return nil
}
func (m *MotorBuilder) SetSeats() BuildProcess {
return nil
}
func (m *MotorBuilder) SetStructure() BuildProcess {
return nil
}
func (m *MotorBuilder) GetVehicle() VehicleProduct {
return VehicleProduct{}
}

5、编写测试用例

针对上面编写的建造过程,我们可以进行如下的测试,同目录下创建creational_test.go文件。

1、首先是测试汽车建造过程,假定最终生产的汽车是具有 4 个轮子,5 个座位,然后结构是Car类型,写入如下代码:

package creational

import "testing"

func TestBuilderPattern(t *testing.T) {

manufacturingComplex := ManufacturingDirector{}

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

car := carBuilder.GetVehicle()

if car.Wheels != 4 {
t.Errorf("Wheels on a car must be 4 and they were %d\n", car.Wheels)
}

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

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

}

我们写了 3 个简单的测试检查是否建造出汽车类型。运行单元测试,结果如下:

$ go test -v .
=== RUN TestBuilderPattern
creational_test.go:16: Wheels on a car must be 4 and they were 0
creational_test.go:20: Structure on a car must be 'Car' and was
creational_test.go:24: Seats on a car must be 5 and they were 0
--- FAIL: TestBuilderPattern (0.00s)
FAIL
FAIL github.com/yuzhoustayhungry/GoDesignPattern/creational 0.860s
FAIL

如上显示,3 个测试单元都显示失败,接着我们来看一下摩托车的单元测试怎么写的。

2、摩托车 motorCycle 的单元测试如下:

motorBuilder := &MotorBuilder{}

manufacturingComplex.SetBuilder(motorBuilder)
manufacturingComplex.Construct()
motorCycle := motorBuilder.GetVehicle()
if motorCycle.Wheels != 2 {
t.Errorf("Wheels on a motorCycle must be 2 and they were %d\n",
motorCycle.Wheels)
}
if motorCycle.Structure != "MotorCycle" {
t.Errorf("Structure on a motorCycle must be 'MotorCycle' and was %s\n",
motorCycle.Structure)
}
if motorCycle.Seats != 2 {
t.Errorf("Seats on a motorCycle must be 2 and was %d\n", motorCycle.Seats)
}

建造过程跟 car 类似,我们只需要向manufacturingComplex.SetBuilder(motorBuilder)传递motorBuilder即可,我们假定摩托车有 2 个轮子,2 个座位,结构必须为MotorCyle。

运行测试代码,得到如下结果:

$ go test -v .
=== RUN TestBuilderPattern
creational_test.go:16: Wheels on a car must be 4 and they were 0
creational_test.go:20: Structure on a car must be 'Car' and was
creational_test.go:24: Seats on a car must be 5 and they were 0
creational_test.go:36: Wheels on a motorCycle must be 2 and they were 0
creational_test.go:41: Structure on a motorCycle must be 'MotorCycle' and was
creational_test.go:46: Seats on a motorCycle must be 2 and was 0
--- FAIL: TestBuilderPattern (0.00s)
FAIL
FAIL github.com/yuzhoustayhungry/GoDesignPattern/creational 0.595s
FAIL

可以看到,单元测试也是失败的,因为我们还没有完成实现具体的建造者模式。接下来就是具体实现的过程。

四、建造者模式 Go 实现

为了实现建造者,想必你也开始有了一点点自己的思路吧。再来实现我们之前创建的creation.go文件,全新的creational.go文件代码如下:

package creational

// 建造过程
type BuildProcess interface {SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
// 制作主管
type ManufacturingDirector struct {
builder BuildProcess
}
func (f *ManufacturingDirector) Construct() {//
f.builder.SetSeats().SetStructure().SetWheels()
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {//
f.builder = b
}
// 产品
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
// 汽车建造者
type CarBuilder struct {
v VehicleProduct
}
func (c *CarBuilder) SetWheels() BuildProcess {// return nil
c.v.Wheels = 4return c
}
func (c *CarBuilder) SetSeats() BuildProcess {// return nil
c.v.Seats = 5return c
}
func (c *CarBuilder) SetStructure() BuildProcess {// return nil
c.v.Structure = "Car"return c
}
func (c *CarBuilder) GetVehicle() VehicleProduct {// return VehicleProduct{}return c.v
}
// 摩托车建造者
type MotorBuilder struct {
v VehicleProduct
}
func (m *MotorBuilder) SetWheels() BuildProcess {// return nil
m.v.Wheels = 2return m
}
func (m *MotorBuilder) SetSeats() BuildProcess {// return nil
m.v.Seats = 2return m
}
func (m *MotorBuilder) SetStructure() BuildProcess {// return nil
m.v.Structure = "MotorCycle"return m
}
func (m *MotorBuilder) GetVehicle() VehicleProduct {// return VehicleProduct{}return m.v
}

更改后的creational_test.go文件如下:

package creational
import "testing"
func TestBuilderPattern(t *testing.T) {
manufacturingComplex := ManufacturingDirector{}
carBuilder := &CarBuilder{}
manufacturingComplex.SetBuilder(carBuilder)
manufacturingComplex.Construct()
car := carBuilder.GetVehicle()
if car.Wheels != 4 {
t.Errorf("Wheels on a car must be 4 and they were %d\n", car.Wheels)}
if car.Structure != "Car" {
t.Errorf("Structure on a car must be 'Car' and was %s\n", car.Structure)}
if car.Seats != 5 {
t.Errorf("Seats on a car must be 5 and they were %d\n", car.Seats)}
motorBuilder := &MotorBuilder{}
manufacturingComplex.SetBuilder(motorBuilder)
manufacturingComplex.Construct()
motorCycle := motorBuilder.GetVehicle()
if motorCycle.Wheels != 2 {
t.Errorf("Wheels on a motorCycle must be 2 and they were %d\n",
motorCycle.Wheels)}
if motorCycle.Structure != "MotorCycle" {
t.Errorf("Structure on a motorCycle must be 'MotorCycle' and was %s\n",
motorCycle.Structure)}
if motorCycle.Seats != 2 {
t.Errorf("Seats on a motorCycle must be 2 and was %d\n", motorCycle.Seats)}
}

实现完所有的方法之后,再看运行go test -v .执行后的测试结果:

$ go test -v .
=== RUN TestBuilderPattern
--- PASS: TestBuilderPattern (0.00s)
PASS
ok github.com/yuzhoustayhungry/GoDesignPattern/creational 0.255s

恭喜,至此,测试用例全部通过。你也可以看到,建造者模式是一个可重复的模式,但在BuildProcess接口的每个方法内,我们可以封装尽可能多的复杂对象,这样,用户其实并不知道关于对象创建的细节。

五、建造者模式总结

就像制作汽车一样,建造者模式的核心在于如何一步一步地构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品。

在软件开发过程中,如果需要创建复杂对象,并希望系统具备很好的灵活性和可扩展性,可以考虑使用建造者模式。

​想了解更多关于开源的内容,请访问:​

​清一色开源基础软件社区​

​https://ost.51cto.com​​。

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月7日 03:58
下一篇 2023年5月7日 03:59

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信