建造者模式 (Builder Pattern)

shaojie lishaojie li
3 min read

概述

建造者模式是一种创建型设计模式,它提供了一种创建复杂对象的最佳方式。该模式允许你使用相同的创建代码生成不同类型和形式的对象,特别适用于需要创建复杂对象且对象的各个部分需要按照一定的步骤创建的场景。

模式结构

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

  1. 产品 (Product): 最终要创建的复杂对象

  2. 抽象建造者 (Builder): 定义创建产品各个部件的抽象接口

  3. 具体建造者 (Concrete Builder): 实现Builder接口,构造和装配各个部件

  4. 指挥者 (Director): 构建一个使用Builder接口的对象,封装构建过程

代码示例说明

1. 产品 (Product)

type Computer struct {
    CPU     string
    RAM     string
    Storage string
    GPU     string
    OS      string
}

func (c *Computer) String() string {
    return fmt.Sprintf("Computer{CPU: %s, RAM: %s, Storage: %s, GPU: %s, OS: %s}",
        c.CPU, c.RAM, c.Storage, c.GPU, c.OS)
}

Computer 是我们要创建的复杂产品,包含多个组件(CPU、RAM、存储、显卡、操作系统)。添加了 String() 方法便于输出显示。

2. 抽象建造者 (Builder Interface)

type Builder interface {
    SetCPU(cpu string) Builder
    SetRAM(ram string) Builder
    SetStorage(storage string) Builder
    SetGPU(gpu string) Builder
    SetOS(os string) Builder
    Build() *Computer
}

定义了构建产品各个部件的抽象接口。每个Set方法都返回Builder自身,支持链式调用。

3. 具体建造者 (Concrete Builder)

type ComputerBuilder struct {
    computer *Computer
}

func NewComputerBuilder() *ComputerBuilder {
    return &ComputerBuilder{
        computer: &Computer{},
    }
}

func (cb *ComputerBuilder) SetCPU(cpu string) Builder {
    cb.computer.CPU = cpu
    return cb
}

func (cb *ComputerBuilder) Build() *Computer {
    // 可以在这里进行验证和设置默认值
    if cb.computer.CPU == "" {
        cb.computer.CPU = "Default CPU"
    }
    if cb.computer.RAM == "" {
        cb.computer.RAM = "8GB"
    }
    if cb.computer.OS == "" {
        cb.computer.OS = "Default OS"
    }
    return cb.computer
}

具体建造者实现了Builder接口,内部维护一个产品对象。关键特点:

  • 每个Set方法都返回自身,支持链式调用

  • Build() 方法可以进行最后的验证和默认值设置

  • 使用构造函数 NewComputerBuilder() 初始化

4. 指挥者 (Director)

type Director struct{}

func (d *Director) BuildGamingComputer(builder Builder) *Computer {
    return builder.
        SetCPU("Intel i9-13900K").
        SetRAM("32GB DDR5").
        SetStorage("1TB NVMe SSD").
        SetGPU("RTX 4080").
        SetOS("Windows 11").
        Build()
}

func (d *Director) BuildOfficeComputer(builder Builder) *Computer {
    return builder.
        SetCPU("Intel i5-12400").
        SetRAM("16GB DDR4").
        SetStorage("512GB SSD").
        SetOS("Windows 11").
        Build()
}

指挥者封装了复杂对象的构建过程,提供了创建特定配置产品的方法。它知道如何使用建造者来创建产品,将构建的具体步骤进行了封装。

为什么Director是空结构体?

生活中的简单例子

想象你去麦当劳点餐:

// 收银员(Director)- 不需要记住任何东西,只知道套餐怎么搭配
type 收银员 struct{} // 空的!

// 厨师(Builder)- 需要记住当前在做什么汉堡
type 厨师 struct {
    当前汉堡 *汉堡  // 需要存储正在制作的汉堡
}

// 收银员只会说固定的话
func (收银员) 推荐巨无霸套餐(厨师 *厨师) *套餐 {
    return 厨师.加汉堡("巨无霸").加薯条("大份").加饮料("可乐").完成()
}

为什么收银员是"空的"?

  1. 收银员不需要记东西

     // 收银员每次都说一样的话,不需要大脑存储
     type Director struct{} // 像收银员,不用记住任何信息
    
     // 如果收银员要记住客户喜好,就需要大脑存储
     type 有记忆的Director struct {
         客户喜好 string // 需要存储信息
     }
    
  2. 节省内存

     // 空结构体 = 不占地方
     var director Director  // 占用 0 字节
    
     // 就像一个不占地方的机器人收银员
     // 1000个这样的机器人也不占空间
    
  3. 只会固定的几句话

     func (d *Director) BuildGamingComputer(builder Builder) *Computer {
         // 就像收银员背台词:"游戏套餐包含..."
         // 不需要思考,每次都说一样的
         return builder.SetCPU("高端").SetRAM("32GB").Build()
     }
    

对比理解

  • Director(收银员):只会背台词,不需要大脑记忆 → 空结构体

  • Builder(厨师):要记住正在做什么汉堡 → 需要字段存储状态

// 收银员:空脑袋,只会说话
type Director struct{}

// 厨师:要记住当前在做什么
type ComputerBuilder struct {
    computer *Computer  // 记住正在组装的电脑
}

最简单的理解: Director就像一个说明书,说明书本身不需要存储任何东西,只是告诉你步骤。而Builder像工人,工人需要记住当前的工作进度。

5. 客户端使用示例

func main() {
    builder := NewComputerBuilder()
    director := &Director{}

    // 手动构建
    customComputer := builder.
        SetCPU("AMD Ryzen 7 5700X").
        SetRAM("16GB DDR4").
        SetStorage("500GB NVMe SSD").
        SetGPU("GTX 1660 Super").
        SetOS("Ubuntu 22.04").
        Build()

    // 使用Director构建
    gamingComputer := director.BuildGamingComputer(NewComputerBuilder())

    // 分步构建
    stepBuilder := NewComputerBuilder()
    stepBuilder.SetCPU("Intel i7-12700K")
    stepBuilder.SetRAM("32GB DDR4")
    stepComputer := stepBuilder.Build()
}

客户端代码展示了三种使用方式:手动链式构建、使用Director构建、分步构建。

优点

  1. 分离构建过程和表示: 使用不同的建造者可以创建不同的产品表示

  2. 更精细的控制: 可以逐步构造产品,控制构造过程的每一步

  3. 代码复用: 相同的构造过程可以创建不同的产品表示

  4. 链式调用: 提供流畅的API接口,代码更易读

  5. 参数验证: 可以在Build()方法中集中进行参数验证和默认值设置

缺点

  1. 增加复杂性: 需要创建多个新类(Builder、Director等)

  2. 代码量增加: 需要为每个可配置属性编写Set方法

  3. 产品必须有共同点: 建造者模式适用的产品应该有足够的共同点

使用场景

  1. 创建复杂对象: 对象有很多属性,且某些属性有复杂的默认值或验证逻辑

  2. 构造参数很多: 避免构造函数参数过多的问题

  3. 需要不同表示: 相同的构建过程需要创建不同的产品表示

  4. 分步构建: 需要分步骤创建对象,每步都有特定的逻辑

Go语言实现特点

  1. 接口实现: 使用Go的隐式接口实现,不需要显式声明

  2. 方法链: 返回Builder接口类型支持链式调用

  3. 构造函数: 使用 NewXxxBuilder() 函数作为构造函数

  4. 错误处理: 可以在Build()方法中返回error进行错误处理

总结

建造者模式特别适合Go语言的编程风格,通过链式调用提供了清晰、易读的API。它解决了复杂对象创建的问题,将对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。在需要创建复杂配置对象时,建造者模式是一个非常好的选择。

0
Subscribe to my newsletter

Read articles from shaojie li directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

shaojie li
shaojie li

Hi there 👋, I'm [mamba] I'm a Speech Recognition Engineer with a passion for machine learning and natural language processing. I love working on impactful projects and am always looking for new challenges.