在Go语言项目里,当我们需要创建一个字段很多、且大部分参数可选的对象时,如果直接写一个大参数的构造函数,代码会很难读也很难维护。建造者模式把对象的创建过程交给一个专门的构造器,让调用方通过一步步设置属性来拿到最终实例,从而把复杂的组装逻辑从业务代码中剥离出来。

为什么需要建造者模式
假设我们有一个服务器配置对象,包含地址、端口、超时时间、最大连接数、是否开启TLS等十几个字段。如果用一个函数把所有参数都传进去,调用者必须记住参数顺序,而且很多字段有默认值,传零值反而容易引发错误。更麻烦的是,当后续新增字段时,构造函数签名一变,所有调用点都要改。
建造者模式用一个独立的Builder类型来收集参数,它通常提供一系列以Set或With开头的方法,每个方法返回Builder自身以便链式调用。最后通过Build方法做合法性校验并生成目标对象。这样调用方只关心自己要改哪些字段,代码意图一目了然,也方便在Build里统一做校验。
标准Builder结构实现
下面用一个ServerConfig的例子展示最直观的建造者写法。我们先定义目标结构体和Builder结构体,Builder持有同样的字段,并对外提供链式设置方法。
package main
import (
"errors"
"fmt"
)
// 目标复杂对象
type ServerConfig struct {
Host string
Port int
TimeoutMs int
MaxConn int
EnableTLS bool
CertFile string
}
// 建造者
type ServerConfigBuilder struct {
host string
port int
timeoutMs int
maxConn int
enableTLS bool
certFile string
}
func NewServerConfigBuilder() *ServerConfigBuilder {
// 设置默认值
return &ServerConfigBuilder{
host: "127.0.0.1",
port: 8080,
timeoutMs: 3000,
maxConn: 100,
enableTLS: false,
}
}
func (b *ServerConfigBuilder) Host(host string) *ServerConfigBuilder {
b.host = host
return b
}
func (b *ServerConfigBuilder) Port(port int) *ServerConfigBuilder {
b.port = port
return b
}
func (b *ServerConfigBuilder) TimeoutMs(ms int) *ServerConfigBuilder {
b.timeoutMs = ms
return b
}
func (b *ServerConfigBuilder) MaxConn(n int) *ServerConfigBuilder {
b.maxConn = n
return b
}
func (b *ServerConfigBuilder) EnableTLS(enable bool, cert string) *ServerConfigBuilder {
b.enableTLS = enable
b.certFile = cert
return b
}
// 最终构建并做校验
func (b *ServerConfigBuilder) Build() (*ServerConfig, error) {
if b.host == "" {
return nil, errors.New("host不能为空")
}
if b.port <= 0 || b.port > 65535 {
return nil, errors.New("port不合法")
}
if b.enableTLS && b.certFile == "" {
return nil, errors.New("开启TLS时必须提供证书文件")
}
return &ServerConfig{
Host: b.host,
Port: b.port,
TimeoutMs: b.timeoutMs,
MaxConn: b.maxConn,
EnableTLS: b.enableTLS,
CertFile: b.certFile,
}, nil
}
func main() {
cfg, err := NewServerConfigBuilder().
Host("api.ipipp.com").
Port(443).
TimeoutMs(5000).
MaxConn(200).
EnableTLS(true, "/etc/cert.pem").
Build()
if err != nil {
fmt.Println("构建失败:", err)
return
}
fmt.Printf("配置: %+vn", cfg)
}
上面的代码中,NewServerConfigBuilder先填入合理的默认值,之后每个设置方法都返回Builder指针,因此可以写成一行链式调用。Build方法集中处理校验,比如端口范围、TLS证书必填等,保证拿到的ServerConfig一定是合法的。
这种写法的优点是结构清晰,调用处不需要了解默认值细节,也不会因为参数位置写错而编译通过却运行异常。缺点是多写了一个Builder结构体,对于特别简单的对象显得啰嗦,所以建造者更适合字段多、有校验、可选参数多的场景。
与函数选项模式对比
Go社区里另一种常见做法是函数选项模式,也就是把设置逻辑写成一系列func(*ServerConfig)的函数,通过可变参数传入构造函数。下面给出对照示例,方便理解二者差异。
package main
import "fmt"
type ServerConfig struct {
Host string
Port int
EnableTLS bool
}
type Option func(*ServerConfig)
func WithHost(h string) Option {
return func(c *ServerConfig) {
c.Host = h
}
}
func WithPort(p int) Option {
return func(c *ServerConfig) {
c.Port = p
}
}
func WithTLS(enable bool) Option {
return func(c *ServerConfig) {
c.EnableTLS = enable
}
}
func NewServerConfig(opts ...Option) *ServerConfig {
cfg := &ServerConfig{
Host: "127.0.0.1",
Port: 8080,
}
for _, opt := range opts {
opt(cfg)
}
return cfg
}
func main() {
cfg := NewServerConfig(
WithHost("api.ipipp.com"),
WithPort(443),
WithTLS(true),
)
fmt.Printf("% +vn", cfg)
}
函数选项模式写起来更轻量,不需要单独的Builder类型,而且扩展新选项不会破坏已有调用。但它把校验分散在各个Option里,或者在New函数末尾统一校验,链式可读性不如Builder直观,也不方便在设置过程中做跨字段的复杂状态判断。
如果你的对象构建逻辑简单、只是填几个可选值,函数选项模式更合适;如果构建步骤多、需要严格校验、且希望调用代码像在描述组装流程,那么标准的建造者模式会更易维护。在Golang中二者可以共存,也可以把Builder内部实现改成用Option来收集参数,兼顾两种风格。
实践中的注意事项
使用建造者模式时要注意Builder本身不是并发安全的,如果多个goroutine同时调用同一个Builder实例的方法,会产生数据竞争。正确做法是为每次构建创建新的Builder,就像前面NewServerConfigBuilder所做的那样。另外,Build方法最好返回值和error,而不是在方法里panic,这样调用方能优雅处理配置错误。
当对象特别复杂时,还可以把Builder按子模块拆分,比如网络配置Builder、存储配置Builder,再由总Builder组合它们,这样每个子Builder职责单一,测试也更方便。只要把握住逐步构建、统一出口校验这两个核心点,就能在Go项目里把建造者模式用得顺手且稳健。
Golang建造者模式builder_pattern修改时间:2026-08-04 18:03:32