函数选项模式本质是用函数类型封装配置逻辑,把零散参数变为可组合、可复用的配置行为;核心为定义type Option func(*T),各WithXXX返回闭包修改指针,NewXXX先设默认值再遍历执行opts。

函数选项模式的本质是啥
它不是语法糖,而是用函数类型封装配置逻辑,把零散的参数变成可组合、可复用的配置行为。核心在于定义一个 Option 类型(通常是函数),再让目标结构体的构造函数接受 []Option 并依次执行。
怎么写一个可用的 Option 类型和 NewXXX 函数
别直接在结构体字段上暴露 setter 方法——那会破坏不可变性,也难做参数校验。正确做法是让每个选项函数接收指针并修改内部状态:
type Server struct {
addr string
timeout int
enableTLS bool
}
type Option func(*Server)
func WithAddr(addr string) Option {
return func(s *Server) {
s.addr = addr
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
if timeout > 0 {
s.timeout = timeout
}
}
}
func NewServer(opts ...Option) *Server {
s := &Server{
addr: "localhost:8080",
timeout: 30,
}
for _, opt := range opts {
opt(s)
}
return s
}
-
Option必须是函数类型,不能是接口或 struct,否则无法用...展开 - 每个
WithXXX函数返回闭包,而不是直接修改——这样才能支持链式调用和复用 -
NewServer内部必须先初始化默认值,再应用选项;顺序很重要,后应用的选项会覆盖前面的
常见踩坑:nil 指针、重复设置、无校验传参
如果某个 Option 函数没做防御性检查,比如传入空字符串或负数 timeout,运行时才 panic 或行为异常,排查成本很高。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 所有
Option函数里都要对输入做基础校验,比如if addr == "" { return } - 避免在
Option中做耗时操作(如读文件、连 DB),它应该只是“配置”,不是“初始化” - 不要在多个
Option里修改同一个字段又不加锁——并发调用NewServer是安全的,但并发传入同一组Option实例并复用,可能出问题 - 如果结构体字段本身是指针(如
*log.Logger),Option函数里要判空,否则s.logger.Println()会 panic
什么时候该用,什么时候不该用
选项模式适合字段多、默认值合理、且不同场景需要不同组合的结构体。如果只有两三个字段,或者几乎每次都要全量传参,硬编码或结构体字面量更直白。
立即学习“go语言免费学习笔记(深入)”;
- HTTP client、DB 连接池、gRPC server 这类基础设施对象,强烈推荐用
- DTO 或纯数据载体(如
User、Order)没必要,反而增加调用复杂度 - Go 1.21+ 支持泛型,若需统一处理多种类型(如
WithOptions[T any]),可以封装通用 apply 逻辑,但多数业务场景不值得过早抽象
真正麻烦的是选项之间的隐式依赖——比如 WithTLS() 要求 addr 必须是 HTTPS 协议,这种约束没法靠类型系统表达,只能靠文档和运行时 panic 提示。这点容易被忽略,但线上出问题时最难 debug。

















