UserBuilder 必须用指针接收者,否则链式调用静默失效;所有 setter 方法需返回 *UserBuilder,NewUserBuilder() 返回指针,Build() 负责校验、默认值填充与深拷贝,且 builder 非线程安全,应每次新建实例。

UserBuilder必须用指针接收者,否则链式调用会静默失效——这是 Go 中 Builder 模式最常踩的坑。
为什么 UserBuilder.Name() 不生效?
常见现象:调用 b.Name("Alice").Age(30) 后 b.Build() 出来的对象字段仍是零值。
根本原因:方法用了值接收者(func (b UserBuilder) Name(name string) UserBuilder),每次调用都在操作副本,原实例没变。
- 所有 setter 方法必须声明为
func (b *UserBuilder) Name(name string) *UserBuilder - 返回
*UserBuilder而不是UserBuilder,否则链式调用在第二步就断开 -
NewUserBuilder()必须返回指针:return &UserBuilder{user: &User{}}
Build() 里该做哪些事?
它不是简单地返回字段,而是唯一可信的构造出口。校验、默认值、深拷贝都得在这一步收口。
- 必填字段检查:如
if b.user.Name == "" { return nil, errors.New("name is required") } - 默认值填充:如
if b.user.Age == 0 { b.user.Age = 18 },别在Age()里硬塞 - 可变字段深拷贝:若
UserBuilder.roles []string,构建时用append([]string{}, b.roles...),而非直接赋值 - 避免返回 builder 内部字段的引用(如
*sql.DB),否则外部修改会影响后续构建结果
要不要用 functional options 替代 UserBuilder?
取决于场景复杂度。字段少、纯配置、无依赖关系时,functional options 更轻量、更 Go 风格。
- 适合:
http.Client、grpc.DialOption这类纯参数组合 - 不适合:需要分步校验(如“启用 TLS 就必须设 CA”)、中间状态复用(先配重试策略,稍后加熔断)、或字段间有强顺序依赖
- 混用可行:用
functional options初始化 builder 实例,再走链式补充逻辑,但需明确边界
并发环境下怎么用 UserBuilder?
它不是线程安全的。builder 实例本质是临时状态容器,多个 goroutine 共享同一个 *UserBuilder 会导致竞态。
- 禁止全局复用单个 builder 实例
- 每个构建任务应新建
*UserBuilder:即NewUserBuilder().Name(...).Build()是安全的 - 如果想复用 builder 逻辑(如测试中预设通用配置),用组合:定义通用 builder 类型,再嵌入到具体 builder 中,不共享实例
容易被忽略的一点:Build() 返回的是新对象,但 builder 内部字段可能仍被持有——如果你在 builder 里存了 map 或 slice 的引用,下次调用 NewUserBuilder() 前没清空,就可能意外复用旧数据。稳妥做法是让 Build() 返回后,builder 实例即废弃,不复用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











