builder 方法必须返回 builder 才能链式调用,所有 with 方法签名应为 func(b builder) withx(x x) *builder,build() 需校验必填字段并明确报错,嵌套结构宜用指针避免零值陷阱。

Builder 函数返回值必须是 *Builder 才能链式调用
Go 没有方法重载,也不支持 this 返回,链式调用全靠显式返回指针。如果 WithTimeout、WithRetry 这类方法返回的是值类型(比如 Builder),下一次调用就作用在副本上,原始对象不变 —— 参数全丢了。
常见错误现象:builder.WithTimeout(5).WithRetry(3) 看似连贯,但 WithRetry 实际改的是前一步返回的新副本,最终 Build 出来的对象里只有 timeout 生效。
- 所有 setter 方法签名必须是
func (b *Builder) WithX(x X) *Builder - 不要在方法里做
b = &Builder{...}这种重新赋值,会切断链式引用 - 如果需要深拷贝逻辑(极少见),得手动 clone 字段,再 return b
Build() 方法要校验必填字段,而不是 defer panic
Builder 的核心价值之一是把运行时错误提前到构建完成时。很多人图省事,在 Build() 里直接 return &Obj{...},等真正用到 obj.Do() 才发现 obj.client == nil panic —— 这违背了 Builder 的契约。
使用场景:配置类对象(如 HTTP 客户端、数据库连接池)、领域模型初始化(如订单、用户)、测试 fixture 构造。
立即学习“go语言免费学习笔记(深入)”;
-
Build()应该做最小必要校验,例如if b.url == "" { return nil, errors.New("url is required") } - 不要依赖调用方记得先调
Validate();Build 就是唯一出口,它得扛住 - 错误信息里避免泛泛的 “invalid config”,明确指出哪个字段缺失或越界
嵌套结构体字段初始化容易漏掉零值覆盖
当 Builder 内部持有嵌套结构(比如 type Config struct { Timeout time.Duration; Retry RetryPolicy }),直接 cfg.Timeout = b.timeout 是安全的;但如果 RetryPolicy 是个 struct,且你只设置了其中一两个字段,其余字段会保留零值 —— 而这可能不是你想要的默认行为。
性能影响:每次 Build() 都 new 一个完整嵌套结构不划算;兼容性上,老代码如果依赖某个嵌套字段的零值语义,新 Builder 改成非零默认值就会 break。
- 对嵌套结构,建议用指针字段(
Retry *RetryPolicy),只在用户显式调用WithRetry()时才 new - 或者提供
WithRetryDefaults()显式覆盖整块默认值,而非隐式填充 - 避免在 Builder 字段里存 struct 值类型,尤其含 slice/map/chan 的——复制开销和语义风险都高
测试 Builder 时别 mock Build(),要测字段组合效果
Builder 不是接口,也不是抽象类,它的行为完全由字段 + Build 逻辑决定。写测试时如果只 mock Build() 返回固定对象,等于绕过了整个构造过程,测了个寂寞。
容易踩的坑:用 reflect.DeepEqual 直接比对最终对象,却忘了 time.Time 或 sync.Mutex 这类不可比较字段导致 panic;或者只测单个 With 方法,没覆盖多参数交叉影响。
- 测试重点应是:不同 With 组合 → Build 后对象字段是否符合预期
- 对不可比较字段(如
http.Client),单独断言其关键属性(client.Timeout) - 加一个“全零值 Build”测试,确认必填字段校验确实触发










