一文搞懂设计模式之建造者模式

大家好,我是晴天,我们又见面了,这周我们继续学习一文搞懂设计模式系列,本周将一起学习建造者模式(生成器模式)

什么是建造者模式

建造者模式(也称为生成器模式)是一种创建型设计模式。

基本概念

建造者模式主要包含以下几个角色:

  1. 产品(Product): 要创建的复杂对象,它由多个部件组成。

  2. 抽象建造者(Builder): 定义了创建产品的抽象接口,包括创建产品的各个部件的方法。

  3. 具体建造者(Concrete Builder): 实现抽象建造者接口,负责实际构建产品的各个部件,通常包含一个用于获取构建完成后产品的方法。

  4. 指导者(Director): 负责使用建造者接口构建产品,通常包含一个构建方法,该方法通过调用建造者的方法按特定顺序构建产品。

  5. 客户端(Client): 使用指导者构建复杂对象的客户端。

基础关系

建造者模式的几个角色之间的关系如下:

  1. 产品(Product): 要创建的复杂对象,它由多个部件组成。产品的内部结构通常由多个部分组成,而具体的部分可以由抽象建造者和具体建造者来定义。

  2. 抽象建造者(Builder): 定义了创建产品的抽象接口,包括创建产品的各个部件的方法。抽象建造者中的方法通常对应产品的各个部分,但是这些方法通常是空的或者有默认实现。

  3. 具体建造者(Concrete Builder): 实现抽象建造者接口,负责实际构建产品的各个部件。具体建造者包含了具体的构建逻辑,它知道如何组装产品的各个部分,并负责最终返回构建好的产品。

  4. 指导者(Director): 负责使用建造者接口构建产品。指导者通常包含一个构建方法,该方法通过调用建造者的方法按照一定的顺序来构建产品。指导者知道构建的步骤和顺序,但它并不知道具体的产品是如何构建的。

  5. 客户端(Client): 客户端通过指导者来构建产品,而不需要知道产品的具体构建过程。客户端创建一个指导者,并将具体的建造者传递给指导者。然后,客户端通过调用指导者的构建方法来获取构建好的产品。

这些角色协同工作,使得构建过程与最终产品的表示相分离。客户端通过指导者来构建产品,而具体的构建过程由具体建造者完成。这种分离使得可以更灵活地改变产品的内部表示,同时客户端不必关心具体的构建细节。

现实举例

一个实际生活中的例子是制造汽车。汽车通常由多个部分组成,例如引擎、底盘、车身、轮胎等。而不同类型的汽车,如轿车、卡车、越野车等,可能具有不同的配置和特征。

在这种情况下,建造者模式可以用于创建不同类型的汽车,而无需改变构建过程的逻辑。让我们看看这个例子的几个角色:

  1. 产品(Product): 汽车是要创建的产品,它由引擎、底盘、车身、轮胎等多个部分组成。

  2. 抽象建造者(Builder): 定义了创建汽车的抽象接口,包括创建汽车的各个部分的方法。例如,有一个抽象方法用于构建引擎、构建底盘、构建车身等。

  3. 具体建造者(Concrete Builder): 实现了抽象建造者接口,负责实际构建汽车的各个部分。比如,轿车建造者负责构建小型引擎和轻巧的车身,而卡车建造者负责构建大型引擎和坚固的底盘。

  4. 指导者(Director): 负责使用建造者接口构建汽车。指导者知道构建汽车的步骤和顺序,但并不知道具体的汽车是如何构建的。它可以接受不同类型的具体建造者,并使用它们来构建不同配置的汽车。

  5. 客户端(Client): 客户端通过创建一个指导者,并将具体建造者传递给指导者,来构建汽车。客户端只需要调用指导者的构建方法,而不需要了解汽车的具体构建过程。

通过这种方式,可以轻松地创建不同类型的汽车,而不必改变指导者的代码。每个具体建造者负责自己类型汽车的构建细节,从而实现了构建过程和产品的解耦。这种模式使得在需要新类型的汽车时,可以添加新的具体建造者,而无需修改现有的构建逻辑。

代码实现

我们以上述实例,来实现一个简单的demo

scss 复制代码
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 语言实现建造者模式的例子,以汽车制造为例:

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 是抽象建造者接口,SedanBuilderTruckBuilder 是具体建造者,Director 是指导者。客户端可以通过创建不同类型的具体建造者来构建不同类型的汽车。这样,可以轻松地扩展和修改汽车的构建过程,而不必改变客户端的代码。

代码优化

上述代码,无论是轿车的建造者还是卡车的建造者,都使用了相同的 Car 结构,并且直接为这个结构中的字段赋值。这可能在一些实际情况下并不是最灵活的做法,尤其是在不同类型的汽车具有不同属性时。

一个更灵活的方式是在 Car 结构中引入一个更通用的字段,例如一个 Options 字段,它可以是一个映射或其他适当的数据结构,以便每个具体建造者可以根据需要添加不同的属性。以下是相应的修改:

scss 复制代码
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)
}

适用场景

建造者模式适用于以下场景:

  1. 创建复杂对象: 当需要创建的对象具有复杂的内部结构,由多个部分组成,并且这些部分之间的组装方式有一定的规律时,可以使用建造者模式。这有助于将复杂对象的构建过程与其表示分离,使得可以更灵活地构建不同的表示。

  2. 避免构造方法的参数列表过长: 当一个类的构造方法需要传递很多参数,而且这些参数之间存在一定的关联关系时,使用建造者模式可以避免构造方法参数列表过长的问题。通过将这些参数封装到一个具体的建造者中,可以更清晰地管理和维护。

  3. 支持不同的构建顺序: 如果需要支持不同的构建顺序来构建对象,可以使用建造者模式。具体建造者可以实现不同的构建步骤,指导者根据需要调用这些步骤,以实现不同的构建顺序。

  4. 构建过程具有复杂的条件逻辑: 当构建对象的过程涉及复杂的条件逻辑,不同的条件可能导致不同的构建步骤时,建造者模式可以提供一种清晰的方式来处理这种复杂性。

建造者模式的优缺点

优点:

  1. 分离构建过程和表示: 建造者模式将一个复杂对象的构建过程与其最终表示相分离。这使得可以更容易地改变产品的内部表示,同时客户端不必关心具体的构建过程。

  2. 更好的控制构建过程: 通过使用指导者来控制构建过程,可以灵活地组合各个部分,实现不同的构建顺序和构建逻辑。这使得可以更好地控制对象的创建过程。

  3. 更容易扩展新的具体建造者: 向系统中添加新的具体建造者相对容易,不需要修改指导者的代码。这符合开闭原则,使得系统更容易扩展。

  4. 更易于改变产品的内部表示: 由于构建过程与最终产品的表示分离,可以更灵活地改变产品的内部结构,而不会对客户端产生影响。

缺点:

  1. 增加了系统的复杂性: 引入了多个角色(指导者、抽象建造者、具体建造者)和接口,从而增加了系统的复杂性。对于简单的对象,建造者模式可能显得过于繁琐。

  2. 可能会产生多余的Builder对象: 如果产品的构建过程比较简单,那么可能会存在一些不必要的具体建造者实现,增加了系统的开销。

  3. 不容易发现构建过程中的错误: 在编译时很难发现具体建造者中构建过程的错误,因为具体建造者的方法通常是在运行时调用的。这可能导致一些在构建过程中的逻辑错误只能在实际运行时被发现。

写在最后

感谢大家的阅读,晴天将继续努力,分享更多有趣且实用的主题,如有错误和纰漏,欢迎留言给予指正。 更多文章敬请关注作者个人公众号 晴天码字。 我们下期不见不散,to be continued...

相关推荐
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml47 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠8 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#