写点什么

一起学习 Go 语言设计模式之建造者模式

作者:宇宙之一粟
  • 2022-10-21
    中国香港
  • 本文字数:3143 字

    阅读完需:约 10 分钟

一起学习 Go 语言设计模式之建造者模式

前言

你去买车,你不会只买一个轮胎、一个发动机、一个方向盘,你买的是一辆包括轮胎、方向盘、发动机、底盘、电气系统和车身等多个部件组成的完整骑车。


在设计模式中,建造者模式就是解决如何将这些部件组装成一辆完整的汽车并返回给用户的设计模式。建造者模式为客户端返回的不是一个简单的产品,而是一个由多个部件组成的复杂产品。

思考如下问题

假设让我们思考如何创建一个房屋对象。建造一栋简单的房屋,你需要建造地板和四面的强,安装房门和窗户,然后再建造一个漂亮的屋顶。


但如果想要一个更宽敞舒适的别墅,还需要有院子、游泳池、植物和其他设施(例如中央空调、排水、供电设备),那又该怎么办呢?



最简单的方法就是扩展房屋的积累,然后创建一系列涵盖所有参数组合的子类。随着房子越复杂,子类越多,任何新增的参数都会让这个层次结构更加复杂。



另外一种方式则无需生成子类,我们可以在房屋基类中创建一个包含所有可能参数的超级构造函数,并用它来控制房屋对象,这种方法确实可以避免生成子类,但它却会造成另外一个问题——构造函数参数太多。


House(windows, doors, rooms, hasSwimPool, hasGarden,...)
复制代码


但并不是所有的房子都需要游泳池,导致绝大部分的参数都没有使用,使得构造函数的声明复杂,调用不整洁


解决方式就是今天要介绍的建造者模式。

建造者模式概念

建造者模式(Builder Pattern),又称生成器模式,是较为复杂的创建者模式,它将客户端与包含多个组成部分的复杂对象的创建过程分离,客户端无需知道复杂对象的内部组成部分与装配方式,只需要知道所需的建造者类型即可。


定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。


结构图如下:



由上图可以知道,建造者模式包含 4 个角色:


  • Builder 抽象建造者:它为创建一个产品 Product 对象的各个部件指定抽象接口,这个接口一般包括两类方法:

  • buildPartX() :用于创建复杂对象的各个部件

  • getResult() :用于返回复杂对象

  • ConcreteBuilder 具体建造者:它实现了 Builder 接口,实现各个部件的具体构造和装配方法,定义并明确其所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象

  • Product 产品角色:它是最终被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义其装配过程。

  • Director 主管、指挥者:指挥者又被称为导演类,定义调用构造步骤的顺序。它负责安排负责对象的建造次序,指挥者和抽象建造者之间存在关联关系,可以在其 construct() 建造方法中调用建造者对象的部件构造与装配方法,完成负责对象的建造。

Go 代码示例

代码组织结构如下:



  1. 首先创建 house.go 文件, 建立 House 这个产品基类,代码如下;


package main
type House struct { windowType string doorType string swimPool string floor int}
复制代码


正像前文所说一眼,房子有窗户、门、游泳池、楼层等部分组成。


  1. 然后创建抽象创建者 iBuilder.go 文件,也是我们的建造者接口,分别定义 4 个 set 和 1 个 getHouse() 方法,代码如下:


package main
type IBuilder interface { setWindowType() setDoorType() setNumFloor() setSwimPool() getHouse() House}
func getBuilder(builderType string) IBuilder { if builderType == "normal" { return newNormalBuilder() }
if builderType == "cottages" { return newCottagesBuilder() } return nil}
复制代码


  1. 新建具体建造者:普通房子 normalBuilder.go,在这个文件中,因为 Go 语言没有继承的概念,所以也需要我们定义跟 House 相同的结构体,然后实现 normalHouse 的构建 :


package main
type NormalBuilder struct { windowType string doorType string swimPool string floor int}
func newNormalBuilder() *NormalBuilder { return &NormalBuilder{}}
func (b *NormalBuilder) setWindowType() { b.windowType = "Wooden Window"}
func (b *NormalBuilder) setDoorType() { b.doorType = "Wooden Door"}
func (b *NormalBuilder) setNumFloor() { b.floor = 3}
func (b *NormalBuilder) setSwimPool() { b.swimPool = "None"}
func (b *NormalBuilder) getHouse() House { return House{ doorType: b.doorType, windowType: b.windowType, swimPool: b.swimPool, floor: b.floor, }}
复制代码


  1. 跟上一步同理,新建别墅具体建设者 cottagesBuilder.go 文件,代码如下:


package main
type cottagesBuilder struct { windowType string doorType string swimPool string floor int}
func newCottagesBuilder() *cottagesBuilder { return &cottagesBuilder{}}
func (b *cottagesBuilder) setWindowType() { b.windowType = "Glass Window"}
func (b *cottagesBuilder) setDoorType() { b.doorType = "Steel Security Door"}
func (b *cottagesBuilder) setNumFloor() { b.floor = 1}
func (b *cottagesBuilder) setSwimPool() { b.swimPool = "Swimming Pool"}
func (b *cottagesBuilder) getHouse() House { return House{ doorType: b.doorType, windowType: b.windowType, swimPool: b.swimPool, floor: b.floor, }}
复制代码


  1. 新建主管 director.go ,主管结构体内也是抽象建造者,其次主管有着 setBuilder()buildHouse() 的职责,最后主管负责安排负责对象的建造次序,比如先确定门、窗、楼层,再考虑是否需要加装泳池。最终代码如下:


package main
type Director struct { builder IBuilder}
func newDirector(b IBuilder) *Director { return &Director{ builder: b, }}
func (d *Director) setBuilder(b IBuilder) { d.builder = b}
func (d *Director) buildHouse() House { d.builder.setDoorType() d.builder.setWindowType() d.builder.setNumFloor() d.builder.setSwimPool() return d.builder.getHouse()}
复制代码


6.新建一个 main.go 文件,测试我们的创建者模式是否正确:


package main
import ( "fmt")
func main() {
normalBuilder := getBuilder("normal") cottagesBuilder := getBuilder("cottages")
director := newDirector(normalBuilder) normalHouse := director.buildHouse()
fmt.Printf("Normal House Door Type: %s\n", normalHouse.doorType) fmt.Printf("Normal House Window Type: %s\n", normalHouse.windowType) fmt.Printf("Normal House SwimPool: %s\n", normalHouse.swimPool) fmt.Printf("Normal House Num Floor: %d\n", normalHouse.floor)
director.setBuilder(cottagesBuilder) cottagesHouse := director.buildHouse()
fmt.Printf("\nCottage House Door Type: %s\n", cottagesHouse.doorType) fmt.Printf("Cottage House Window Type: %s\n", cottagesHouse.windowType) fmt.Printf("Cottage House SwimPool: %s\n", cottagesHouse.swimPool) fmt.Printf("Cottage House Num Floor: %d\n", cottagesHouse.floor)
}
复制代码


  1. 最后,我们使用命令运行整个包 go run .


这是输出结果图:


优缺点

优点:


  • 你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。

  • 生成不同形式的产品时, 你可以复用相同的制造代码。

  • 单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。


缺点:


  • 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。


希望本文能对你有所帮助,如果喜欢本文,可以点个关注.下一篇文章见!宇宙古今无有穷期,一生不过须臾,当思奋争。


参考链接:


发布于: 刚刚阅读数: 4
用户头像

宇宙古今无有穷期,一生不过须臾,当思奋争 2020-05-07 加入

🏆InfoQ写作平台-签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于外企开发,弹性不加班 热衷分享,执着于阅读写作,佛系不水文 同名公众号:《宇宙之一粟》

评论

发布
暂无评论
一起学习 Go 语言设计模式之建造者模式_设计模式_宇宙之一粟_InfoQ写作社区