Option模式是用函数值封装配置逻辑的惯用法,核心为定义Option函数类型并按序应用;须避免结构体字段式设计,确保链式调用与惰性求值;构造函数需提供合理零值并支持...Option变参。

Option模式本质是函数式参数构造
Go 没有可选参数语法,Option 模式不是语言特性,而是用函数值封装配置逻辑的惯用法。核心在于定义一个函数类型(如 type Option func(*Config)),每个选项函数接收指针并修改其字段,最终在构造函数中按序应用所有 Option。
必须用函数类型而非结构体字段
常见错误是把 Option 设计成带字段的 struct(比如 WithTimeout(time.Duration) 返回一个 struct),这会导致无法链式调用、难以组合、且失去“惰性求值”能力。正确做法是让每个选项返回一个 Option 函数:
type Option func(*Client)func WithTimeout(d time.Duration) Option { return func(c *Client) { c.timeout = d } }
func WithRetry(max int) Option { return func(c *Client) { c.maxRetries = max } }
- 所有
Option类型一致,可统一接收为...Option变参 - 函数体内可做参数校验、默认值覆盖、甚至副作用(如注册回调)
- 调用顺序影响最终状态——后应用的
Option会覆盖前面同字段的设置
构造函数要支持零值 + Options 组合
构造函数不能只依赖 Option,必须提供合理零值,并允许用户只传部分配置。典型签名是:NewClient(opts ...Option) *Client。内部流程应为:
func NewClient(opts ...Option) *Client {
c := &Client{
timeout: 5 * time.Second,
maxRetries: 3,
// 其他默认值
}
for _, opt := range opts {
opt(c)
}
return c
}- 避免在
Option函数里做资源初始化(如打开文件、连接 DB),应放在构造函数末尾或单独Init()方法中 - 如果某个
Option需要前置校验(如 URL 必须非空),应在构造函数中先遍历一次opts执行校验逻辑,再真正应用 - 不建议在
Option中 panic;应返回 error 并由构造函数统一处理(但这样就破坏了纯函数签名,需权衡)
嵌套结构或第三方依赖时 Option 易失控
当 Config 包含嵌套 struct(如 HTTPClient、Logger)或需要注入接口实现时,直接暴露底层字段会让 Option 膨胀且耦合变高。更可控的做法是:
立即学习“go语言免费学习笔记(深入)”;
- 对嵌套对象也定义专属
Option类型(如HTTPOption),并在主Option中封装调用 - 用
WithLogger(log Logger)直接注入依赖,而不是WithLogLevel(lvl string)—— 后者限制了日志器灵活性 - 避免出现
WithCustomHTTPClientRoundTripper(rt http.RoundTripper)这类过深穿透的选项;应由用户自行构造完整http.Client,再通过WithHTTPClient(*http.Client)注入
真正难的不是写几个 WithXxx,而是判断哪些该暴露、哪些该封装、哪些根本不该让用户碰——这取决于 API 的稳定边界和演化成本。










