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

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

什么是建造者模式

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

基本概念

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

  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...

相关推荐
9523613 分钟前
SpringBoot统一功能处理
java·spring boot·后端
rleS IONS34 分钟前
SpringBoot中自定义Starter
java·spring boot·后端
DevilSeagull1 小时前
MySQL(2) 客户端工具和建库
开发语言·数据库·后端·mysql·服务
TeDi TIVE2 小时前
springboot和springframework版本依赖关系
java·spring boot·后端
雨辰AI2 小时前
SpringBoot3 + 人大金仓 V9 微服务监控实战|Prometheus+Grafana+SkyWalking 全链路监控
数据库·后端·微服务·grafana·prometheus·skywalking
Nicander2 小时前
理解 mybatis 源码:vibe-coding一个mini-mybatis
后端·mybatis
小呆呆6663 小时前
Codex 穷鬼大救星
前端·人工智能·后端
FelixBitSoul4 小时前
缓存淘汰策略全解:从原理到手写实现(Java / Go / Python)
后端·面试
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题】【Java基础篇】第29题:静态代理和动态代理的区别是什么
java·开发语言·后端·面试·代理模式
dovens5 小时前
SpringBoot集成MQTT客户端
java·spring boot·后端