大家好,我是晴天,我们又见面了,这周我们继续学习一文搞懂设计模式系列,本周将一起学习建造者模式(生成器模式)
什么是建造者模式
建造者模式(也称为生成器模式)是一种创建型设计模式。
基本概念
建造者模式主要包含以下几个角色:
-
产品(Product): 要创建的复杂对象,它由多个部件组成。
-
抽象建造者(Builder): 定义了创建产品的抽象接口,包括创建产品的各个部件的方法。
-
具体建造者(Concrete Builder): 实现抽象建造者接口,负责实际构建产品的各个部件,通常包含一个用于获取构建完成后产品的方法。
-
指导者(Director): 负责使用建造者接口构建产品,通常包含一个构建方法,该方法通过调用建造者的方法按特定顺序构建产品。
-
客户端(Client): 使用指导者构建复杂对象的客户端。
基础关系
建造者模式的几个角色之间的关系如下:
-
产品(Product): 要创建的复杂对象,它由多个部件组成。产品的内部结构通常由多个部分组成,而具体的部分可以由抽象建造者和具体建造者来定义。
-
抽象建造者(Builder): 定义了创建产品的抽象接口,包括创建产品的各个部件的方法。抽象建造者中的方法通常对应产品的各个部分,但是这些方法通常是空的或者有默认实现。
-
具体建造者(Concrete Builder): 实现抽象建造者接口,负责实际构建产品的各个部件。具体建造者包含了具体的构建逻辑,它知道如何组装产品的各个部分,并负责最终返回构建好的产品。
-
指导者(Director): 负责使用建造者接口构建产品。指导者通常包含一个构建方法,该方法通过调用建造者的方法按照一定的顺序来构建产品。指导者知道构建的步骤和顺序,但它并不知道具体的产品是如何构建的。
-
客户端(Client): 客户端通过指导者来构建产品,而不需要知道产品的具体构建过程。客户端创建一个指导者,并将具体的建造者传递给指导者。然后,客户端通过调用指导者的构建方法来获取构建好的产品。
这些角色协同工作,使得构建过程与最终产品的表示相分离。客户端通过指导者来构建产品,而具体的构建过程由具体建造者完成。这种分离使得可以更灵活地改变产品的内部表示,同时客户端不必关心具体的构建细节。
现实举例
一个实际生活中的例子是制造汽车。汽车通常由多个部分组成,例如引擎、底盘、车身、轮胎等。而不同类型的汽车,如轿车、卡车、越野车等,可能具有不同的配置和特征。
在这种情况下,建造者模式可以用于创建不同类型的汽车,而无需改变构建过程的逻辑。让我们看看这个例子的几个角色:
-
产品(Product): 汽车是要创建的产品,它由引擎、底盘、车身、轮胎等多个部分组成。
-
抽象建造者(Builder): 定义了创建汽车的抽象接口,包括创建汽车的各个部分的方法。例如,有一个抽象方法用于构建引擎、构建底盘、构建车身等。
-
具体建造者(Concrete Builder): 实现了抽象建造者接口,负责实际构建汽车的各个部分。比如,轿车建造者负责构建小型引擎和轻巧的车身,而卡车建造者负责构建大型引擎和坚固的底盘。
-
指导者(Director): 负责使用建造者接口构建汽车。指导者知道构建汽车的步骤和顺序,但并不知道具体的汽车是如何构建的。它可以接受不同类型的具体建造者,并使用它们来构建不同配置的汽车。
-
客户端(Client): 客户端通过创建一个指导者,并将具体建造者传递给指导者,来构建汽车。客户端只需要调用指导者的构建方法,而不需要了解汽车的具体构建过程。
通过这种方式,可以轻松地创建不同类型的汽车,而不必改变指导者的代码。每个具体建造者负责自己类型汽车的构建细节,从而实现了构建过程和产品的解耦。这种模式使得在需要新类型的汽车时,可以添加新的具体建造者,而无需修改现有的构建逻辑。
代码实现
我们以上述实例,来实现一个简单的demo
package main
import "fmt"
// 产品:汽车
type Car struct {
Engine string
Chassis string
Body string
Tires string
}
// 抽象建造者接口
type Builder interface {
BuildEngine()
BuildChassis()
BuildBody()
BuildTires()
GetCar() *Car
}
// 具体建造者:轿车
type SedanBuilder struct {
car *Car
}
func NewSedanBuilder() *SedanBuilder {
return &SedanBuilder{car: &Car{}}
}
func (sb *SedanBuilder) BuildEngine() {
sb.car.Engine = "Small Engine"
}
func (sb *SedanBuilder) BuildChassis() {
sb.car.Chassis = "Light Chassis"
}
func (sb *SedanBuilder) BuildBody() {
sb.car.Body = "Sleek Body"
}
func (sb *SedanBuilder) BuildTires() {
sb.car.Tires = "Standard Tires"
}
func (sb *SedanBuilder) GetCar() *Car {
return sb.car
}
// 具体建造者:卡车
type TruckBuilder struct {
car *Car
}
func NewTruckBuilder() *TruckBuilder {
return &TruckBuilder{car: &Car{}}
}
func (tb *TruckBuilder) BuildEngine() {
tb.car.Engine = "Large Engine"
}
func (tb *TruckBuilder) BuildChassis() {
tb.car.Chassis = "Heavy Chassis"
}
func (tb *TruckBuilder) BuildBody() {
tb.car.Body = "Robust Body"
}
func (tb *TruckBuilder) BuildTires() {
tb.car.Tires = "Off-road Tires"
}
func (tb *TruckBuilder) GetCar() *Car {
return tb.car
}
// 指导者
type Director struct {
builder Builder
}
func NewDirector(builder Builder) *Director {
return &Director{builder: builder}
}
func (d *Director) Construct() *Car {
d.builder.BuildEngine()
d.builder.BuildChassis()
d.builder.BuildBody()
d.builder.BuildTires()
return d.builder.GetCar()
}
func main() {
// 构建轿车
sedanBuilder := NewSedanBuilder()
director := NewDirector(sedanBuilder)
sedanCar := director.Construct()
fmt.Println("Sedan Car:", sedanCar)
// 构建卡车
truckBuilder := NewTruckBuilder()
director = NewDirector(truckBuilder)
truckCar := director.Construct()
fmt.Println("Truck Car:", truckCar)
}
代码解释
以下是一个简化的使用 Go 语言实现建造者模式的例子,以汽车制造为例:
package main
import "fmt"
// 产品:汽车
type Car struct {
Engine string
Chassis string
Body string
Tires string
}
// 抽象建造者接口
type Builder interface {
BuildEngine()
BuildChassis()
BuildBody()
BuildTires()
GetCar() *Car
}
// 具体建造者:轿车
type SedanBuilder struct {
car *Car
}
func NewSedanBuilder() *SedanBuilder {
return &SedanBuilder{car: &Car{}}
}
func (sb *SedanBuilder) BuildEngine() {
sb.car.Engine = "Small Engine"
}
func (sb *SedanBuilder) BuildChassis() {
sb.car.Chassis = "Light Chassis"
}
func (sb *SedanBuilder) BuildBody() {
sb.car.Body = "Sleek Body"
}
func (sb *SedanBuilder) BuildTires() {
sb.car.Tires = "Standard Tires"
}
func (sb *SedanBuilder) GetCar() *Car {
return sb.car
}
// 具体建造者:卡车
type TruckBuilder struct {
car *Car
}
func NewTruckBuilder() *TruckBuilder {
return &TruckBuilder{car: &Car{}}
}
func (tb *TruckBuilder) BuildEngine() {
tb.car.Engine = "Large Engine"
}
func (tb *TruckBuilder) BuildChassis() {
tb.car.Chassis = "Heavy Chassis"
}
func (tb *TruckBuilder) BuildBody() {
tb.car.Body = "Robust Body"
}
func (tb *TruckBuilder) BuildTires() {
tb.car.Tires = "Off-road Tires"
}
func (tb *TruckBuilder) GetCar() *Car {
return tb.car
}
// 指导者
type Director struct {
builder Builder
}
func NewDirector(builder Builder) *Director {
return &Director{builder: builder}
}
func (d *Director) Construct() *Car {
d.builder.BuildEngine()
d.builder.BuildChassis()
d.builder.BuildBody()
d.builder.BuildTires()
return d.builder.GetCar()
}
func main() {
// 构建轿车
sedanBuilder := NewSedanBuilder()
director := NewDirector(sedanBuilder)
sedanCar := director.Construct()
fmt.Println("Sedan Car:", sedanCar)
// 构建卡车
truckBuilder := NewTruckBuilder()
director = NewDirector(truckBuilder)
truckCar := director.Construct()
fmt.Println("Truck Car:", truckCar)
}
在这个例子中,Car
表示产品,Builder
是抽象建造者接口,SedanBuilder
和 TruckBuilder
是具体建造者,Director
是指导者。客户端可以通过创建不同类型的具体建造者来构建不同类型的汽车。这样,可以轻松地扩展和修改汽车的构建过程,而不必改变客户端的代码。
代码优化
上述代码,无论是轿车的建造者还是卡车的建造者,都使用了相同的 Car
结构,并且直接为这个结构中的字段赋值。这可能在一些实际情况下并不是最灵活的做法,尤其是在不同类型的汽车具有不同属性时。
一个更灵活的方式是在 Car
结构中引入一个更通用的字段,例如一个 Options
字段,它可以是一个映射或其他适当的数据结构,以便每个具体建造者可以根据需要添加不同的属性。以下是相应的修改:
package main
import (
"fmt"
)
// 产品:汽车
type Car struct {
Options map[string]string
}
// 抽象建造者接口
type Builder interface {
BuildEngine()
BuildChassis()
BuildBody()
BuildTires()
GetCar() *Car
}
// 具体建造者:轿车
type SedanBuilder struct {
car *Car
}
func NewSedanBuilder() *SedanBuilder {
return &SedanBuilder{car: &Car{Options: make(map[string]string)}}
}
func (sb *SedanBuilder) BuildEngine() {
sb.car.Options["Engine"] = "Small Engine"
}
func (sb *SedanBuilder) BuildChassis() {
sb.car.Options["Chassis"] = "Light Chassis"
}
func (sb *SedanBuilder) BuildBody() {
sb.car.Options["Body"] = "Sleek Body"
}
func (sb *SedanBuilder) BuildTires() {
sb.car.Options["Tires"] = "Standard Tires"
}
func (sb *SedanBuilder) GetCar() *Car {
return sb.car
}
// 具体建造者:卡车
type TruckBuilder struct {
car *Car
}
func NewTruckBuilder() *TruckBuilder {
return &TruckBuilder{car: &Car{Options: make(map[string]string)}}
}
func (tb *TruckBuilder) BuildEngine() {
tb.car.Options["Engine"] = "Large Engine"
}
func (tb *TruckBuilder) BuildChassis() {
tb.car.Options["Chassis"] = "Heavy Chassis"
}
func (tb *TruckBuilder) BuildBody() {
tb.car.Options["Body"] = "Robust Body"
}
func (tb *TruckBuilder) BuildTires() {
tb.car.Options["Tires"] = "Off-road Tires"
}
func (tb *TruckBuilder) GetCar() *Car {
return tb.car
}
// 指导者
type Director struct {
builder Builder
}
func NewDirector(builder Builder) *Director {
return &Director{builder: builder}
}
func (d *Director) Construct() *Car {
d.builder.BuildEngine()
d.builder.BuildChassis()
d.builder.BuildBody()
d.builder.BuildTires()
return d.builder.GetCar()
}
func main() {
// 构建轿车
sedanBuilder := NewSedanBuilder()
director := NewDirector(sedanBuilder)
sedanCar := director.Construct()
fmt.Println("Sedan Car:", sedanCar)
// 构建卡车
truckBuilder := NewTruckBuilder()
director = NewDirector(truckBuilder)
truckCar := director.Construct()
fmt.Println("Truck Car:", truckCar)
}
适用场景
建造者模式适用于以下场景:
-
创建复杂对象: 当需要创建的对象具有复杂的内部结构,由多个部分组成,并且这些部分之间的组装方式有一定的规律时,可以使用建造者模式。这有助于将复杂对象的构建过程与其表示分离,使得可以更灵活地构建不同的表示。
-
避免构造方法的参数列表过长: 当一个类的构造方法需要传递很多参数,而且这些参数之间存在一定的关联关系时,使用建造者模式可以避免构造方法参数列表过长的问题。通过将这些参数封装到一个具体的建造者中,可以更清晰地管理和维护。
-
支持不同的构建顺序: 如果需要支持不同的构建顺序来构建对象,可以使用建造者模式。具体建造者可以实现不同的构建步骤,指导者根据需要调用这些步骤,以实现不同的构建顺序。
-
构建过程具有复杂的条件逻辑: 当构建对象的过程涉及复杂的条件逻辑,不同的条件可能导致不同的构建步骤时,建造者模式可以提供一种清晰的方式来处理这种复杂性。
建造者模式的优缺点
优点:
-
分离构建过程和表示: 建造者模式将一个复杂对象的构建过程与其最终表示相分离。这使得可以更容易地改变产品的内部表示,同时客户端不必关心具体的构建过程。
-
更好的控制构建过程: 通过使用指导者来控制构建过程,可以灵活地组合各个部分,实现不同的构建顺序和构建逻辑。这使得可以更好地控制对象的创建过程。
-
更容易扩展新的具体建造者: 向系统中添加新的具体建造者相对容易,不需要修改指导者的代码。这符合开闭原则,使得系统更容易扩展。
-
更易于改变产品的内部表示: 由于构建过程与最终产品的表示分离,可以更灵活地改变产品的内部结构,而不会对客户端产生影响。
缺点:
-
增加了系统的复杂性: 引入了多个角色(指导者、抽象建造者、具体建造者)和接口,从而增加了系统的复杂性。对于简单的对象,建造者模式可能显得过于繁琐。
-
可能会产生多余的Builder对象: 如果产品的构建过程比较简单,那么可能会存在一些不必要的具体建造者实现,增加了系统的开销。
-
不容易发现构建过程中的错误: 在编译时很难发现具体建造者中构建过程的错误,因为具体建造者的方法通常是在运行时调用的。这可能导致一些在构建过程中的逻辑错误只能在实际运行时被发现。
写在最后
感谢大家的阅读,晴天将继续努力,分享更多有趣且实用的主题,如有错误和纰漏,欢迎留言给予指正。 更多文章敬请关注作者个人公众号 晴天码字。 我们下期不见不散,to be continued…