函数选项模式的核心是Option类型必须为func(*Config),构造函数须传指针,WithXXX函数须返回操作指针的闭包,否则配置静默失效;默认值统一在NewConfig中初始化,Option仅覆盖不校验。

直接说结论:函数选项模式不是“写几个 WithXXX 函数”就完事,核心在于 Option 类型必须是 func(*T),且构造函数里必须用指针传入结构体实例——漏掉任一环,配置就静默失效。
Option 类型定义必须是 func(*Config),不能是值传递
常见错误是把 Option 定义成 func(Config) Config 或 func(Config)。编译能过,但运行时所有配置都不生效:因为每次调用都操作副本,原始对象内存没被修改。
- 正确写法:
type Option func(*Config),闭包内操作的是真实地址 - 构造函数里必须传
&Config{}或new(Config),不能传Config{} - 如果误写成
opt(c)且c是值类型变量,调试时几乎无法定位——没有 panic,也没有日志,只是“配置没起作用”
WithXXX 函数必须返回闭包,且闭包参数是 *Config
每个选项函数(如 WithTimeout)本质是工厂函数:它不立即设置字段,而是返回一个将来会被调用的闭包。这个闭包的唯一参数必须是 *Config。
- 错误示范:
func WithTimeout(d time.Duration) { c.Timeout = d }—— 缺少返回值,无法作为参数传给NewClient - 正确写法:
func WithTimeout(d time.Duration) Option { return func(c *Config) { c.Timeout = d } } - 如果字段是可空类型(如
*time.Duration),WithTimeout(0)就明确表示“设为零值”,而非“未设置”——语义清晰,无需额外标记字段是否已配置
默认值统一在构造函数里初始化,不在 Option 中校验
别在 WithTimeout 里写 if d == 0 { d = defaultTimeout }。这会混淆意图:用户显式传 0 是想禁用超时,还是手误?
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 所有默认值只在
NewConfig()开头一次性设好,例如:c.Timeout = 30 * time.Second - Option 只负责“覆盖”,不参与“兜底”或“修正”
- 校验逻辑(如
Timeout 报错)统一放在所有 Option 应用完毕后,避免某个 Option 依赖另一个尚未执行的配置 - 多个 Option 有依赖关系时(如
WithPath依赖BaseURL已设置),文档必须注明调用顺序,或改用组合型 Option(如WithURL(base, path))
复杂字段(切片、map、嵌套结构)要深拷贝或防御性复制
如果 Config 里有 AccountList []string 这类字段,WithAccountList(accounts) 直接赋值会导致外部修改影响内部状态。
- 应做防御性复制:
c.AccountList = append([]string(nil), accounts...) - 对 map 字段同理:
c.Headers = map[string]string{}后逐个copy,而非c.Headers = headers - 嵌套结构体字段(如
TLSConfig *tls.Config)若允许传 nil,Option 内部需判空,避免 panic
最易被忽略的点:Option 的执行时机完全由调用方控制,构造函数不保证任何执行顺序以外的约束。一旦出现字段间强依赖,靠文档提醒不如重构接口——比如把 WithBaseURL 和 WithPath 合并为 WithURL,从根源上消除歧义。

















