建造者模式适用于多可选参数、需校验与分步配置的场景,如HTTPClient;工厂模式仅适合参数固定、变体极少的情况;二者组合时应通过私有字段、链式方法和Build()校验实现可控扩展。

工厂模式管“造什么”,建造者模式管“怎么造”——选错就容易写一堆冗余代码或失去配置灵活性。
什么时候该用 NewHTTPClientBuilder() 而不是 NewHTTPClient()?
当对象初始化需要多个可选参数、依赖注入、校验逻辑或分步设置时,直接构造函数会迅速失控。比如 HTTPClient 至少涉及 Timeout、Retry、Middlewares、Transport 等 5+ 配置项,且不同业务场景组合差异大。
- 用工厂函数(如
NewHTTPClient())只适合参数固定、变体极少的场景(例如只有一种默认客户端) - 用建造者(如
NewHTTPClientBuilder().WithTimeout(30 * time.Second).WithRetry(3).Build())才能支持链式、可读、可复用的配置流 - 若还需区分“测试用客户端”“生产用客户端”“Mock 客户端”,那就该让工厂返回建造者:
NewClientBuilder("prod")→ 返回带默认超时/重试/日志中间件的*HTTPClientBuilder
为什么不能只用工厂模式支持多类型 + 多配置?
工厂模式本身不处理“配置过程”,它只负责“分发”。一旦你试图在工厂里塞进所有配置逻辑(比如 NewHTTPClient(timeout, retry, middleware...)),就会立刻掉进两个坑:
- 参数爆炸:新增一个
MaxIdleConns就得改函数签名,所有调用点都要动 - 必填/可选边界模糊:哪些字段必须设?哪些有默认值?工厂函数无法表达这种约束
- 类型安全丢失:用
map[string]interface{}或结构体传参,编译期无法检查字段合法性
而建造者把校验和默认值封装在 Build() 里,比如未设 Timeout 时返回 error,或自动设为 30s,这才是可控的扩展方式。
立即学习“go语言免费学习笔记(深入)”;
工厂 + 建造者组合的最小可行结构长什么样?
核心就三样:统一接口、具体建造者、工厂函数。不需要 Director,也不需要抽象 Builder 接口——Go 里靠值语义和函数链式调用更轻量。
type LoggerBuilder interface {
WithLevel(level string) LoggerBuilder
WithFormat(format string) LoggerBuilder
Build() (Logger, error)
}
type fileLoggerBuilder struct {
level string
format string
path string
}
func (b *fileLoggerBuilder) WithLevel(level string) LoggerBuilder {
b.level = level
return b
}
// ... 其他方法
func NewLoggerBuilder(kind string) LoggerBuilder {
switch kind {
case "file":
return &fileLoggerBuilder{level: "info"} // 默认值在此
case "console":
return &consoleLoggerBuilder{level: "debug"}
default:
panic("unknown logger kind")
}
}
- 工厂函数名要体现意图,比如
NewLoggerBuilder()比NewBuilder()更明确 - 建造者内部字段应私有,避免外部直接修改;
Build()是唯一出口,也是校验入口 - 不要返回指针给建造者(如
*fileLoggerBuilder),除非你确认并发安全;值接收器 + 每次返回新实例更稳妥
最容易被忽略的是:建造者不是为“炫技”而存在,而是为“防止配置遗漏”和“降低调用方认知负担”。如果一个对象创建后 90% 的调用都用同一组参数,那它大概率不该用建造者;但如果每次初始化都要查文档确认字段含义,那就该立刻重构出建造者——哪怕只封装三个字段。










