go-functional-options
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFunctional Options Pattern
函数选项模式
Source: Uber Go Style Guide
Functional options is a pattern where you declare an opaque type that records information in an internal struct. The constructor accepts a variadic number of these options and applies them to configure the result.
Option来源:Uber Go Style Guide
函数选项是一种设计模式,你需要声明一个不透明的类型,该类型会在内部结构体中记录配置信息。构造函数接受可变数量的这类选项,并应用它们来配置最终实例。
OptionWhen to Use
适用场景
Use functional options when:
- 3+ optional arguments on constructors or public APIs
- Extensible APIs that may gain new options over time
- Clean caller experience is important (no need to pass defaults)
在以下场景中使用函数选项模式:
- 3个及以上可选参数的构造函数或公共API
- 需要可扩展的API,未来可能新增配置项
- 重视简洁的调用体验(无需传递默认值)
The Pattern
模式实现
Core Components
核心组件
- Unexported struct - holds all configuration
options - Exported interface - with unexported
Optionmethodapply - Option types - implement the interface
- constructors - create options
With*
- 未导出的结构体 - 存储所有配置信息
options - 导出的接口 - 包含未导出的
Option方法apply - 选项类型 - 实现上述接口
- 构造函数 - 创建配置选项
With*
Option Interface
选项接口
go
type Option interface {
apply(*options)
}The unexported method ensures only options from this package can be used.
applygo
type Option interface {
apply(*options)
}未导出的方法确保只有当前包内的选项可以被使用。
applyComplete Implementation
完整实现示例
Source: Uber Go Style Guide
go
package db
import "go.uber.org/zap"
// options holds all configuration for opening a connection.
type options struct {
cache bool
logger *zap.Logger
}
// Option configures how we open the connection.
type Option interface {
apply(*options)
}
// cacheOption implements Option for cache setting (simple type alias).
type cacheOption bool
func (c cacheOption) apply(opts *options) {
opts.cache = bool(c)
}
// WithCache enables or disables caching.
func WithCache(c bool) Option {
return cacheOption(c)
}
// loggerOption implements Option for logger setting (struct for pointers).
type loggerOption struct {
Log *zap.Logger
}
func (l loggerOption) apply(opts *options) {
opts.logger = l.Log
}
// WithLogger sets the logger for the connection.
func WithLogger(log *zap.Logger) Option {
return loggerOption{Log: log}
}
// Open creates a connection.
func Open(addr string, opts ...Option) (*Connection, error) {
// Start with defaults
options := options{
cache: defaultCache,
logger: zap.NewNop(),
}
// Apply all provided options
for _, o := range opts {
o.apply(&options)
}
// Use options.cache and options.logger...
return &Connection{}, nil
}来源:Uber Go Style Guide
go
package db
import "go.uber.org/zap"
// options holds all configuration for opening a connection.
type options struct {
cache bool
logger *zap.Logger
}
// Option configures how we open the connection.
type Option interface {
apply(*options)
}
// cacheOption implements Option for cache setting (simple type alias).
type cacheOption bool
func (c cacheOption) apply(opts *options) {
opts.cache = bool(c)
}
// WithCache enables or disables caching.
func WithCache(c bool) Option {
return cacheOption(c)
}
// loggerOption implements Option for logger setting (struct for pointers).
type loggerOption struct {
Log *zap.Logger
}
func (l loggerOption) apply(opts *options) {
opts.logger = l.Log
}
// WithLogger sets the logger for the connection.
func WithLogger(log *zap.Logger) Option {
return loggerOption{Log: log}
}
// Open creates a connection.
func Open(addr string, opts ...Option) (*Connection, error) {
// Start with defaults
options := options{
cache: defaultCache,
logger: zap.NewNop(),
}
// Apply all provided options
for _, o := range opts {
o.apply(&options)
}
// Use options.cache and options.logger...
return &Connection{}, nil
}Usage Examples
用法示例
Source: Uber Go Style Guide
来源:Uber Go Style Guide
Without Functional Options (Bad)
不使用函数选项模式(不推荐)
go
// Caller must always provide all parameters, even defaults
db.Open(addr, db.DefaultCache, zap.NewNop())
db.Open(addr, db.DefaultCache, log)
db.Open(addr, false /* cache */, zap.NewNop())
db.Open(addr, false /* cache */, log)go
// Caller must always provide all parameters, even defaults
db.Open(addr, db.DefaultCache, zap.NewNop())
db.Open(addr, db.DefaultCache, log)
db.Open(addr, false /* cache */, zap.NewNop())
db.Open(addr, false /* cache */, log)With Functional Options (Good)
使用函数选项模式(推荐)
go
// Only provide options when needed
db.Open(addr)
db.Open(addr, db.WithLogger(log))
db.Open(addr, db.WithCache(false))
db.Open(
addr,
db.WithCache(false),
db.WithLogger(log),
)go
// Only provide options when needed
db.Open(addr)
db.Open(addr, db.WithLogger(log))
db.Open(addr, db.WithCache(false))
db.Open(
addr,
db.WithCache(false),
db.WithLogger(log),
)Comparison: Functional Options vs Config Struct
对比:函数选项模式 vs 配置结构体模式
| Aspect | Functional Options | Config Struct |
|---|---|---|
| Extensibility | Add new | Add new fields (may break) |
| Defaults | Built into constructor | Zero values or separate defaults |
| Caller experience | Only specify what differs | Must construct entire struct |
| Testability | Options are comparable | Struct comparison |
| Complexity | More boilerplate | Simpler setup |
Prefer Config Struct when: Fewer than 3 options, options rarely change, all options usually specified together, or internal APIs only.
| 对比维度 | 函数选项模式 | 配置结构体模式 |
|---|---|---|
| 可扩展性 | 新增 | 新增字段(可能导致兼容性问题) |
| 默认值处理 | 内置在构造函数中 | 零值或单独维护默认值 |
| 调用方体验 | 仅需指定与默认值不同的配置 | 必须构造完整的结构体 |
| 可测试性 | 选项可直接比较 | 结构体比较 |
| 复杂度 | 需编写更多模板代码 | 实现更简单 |
优先选择配置结构体的场景:可选参数少于3个、配置项极少变更、通常需要指定所有配置项,或仅用于内部API时。
Why Not Closures?
为什么不使用闭包?
Source: Uber Go Style Guide
An alternative implementation uses closures:
go
// Closure approach (not recommended)
type Option func(*options)
func WithCache(c bool) Option {
return func(o *options) { o.cache = c }
}The interface approach is preferred because:
- Testability - Options can be compared in tests and mocks
- Debuggability - Options can implement
fmt.Stringer - Flexibility - Options can implement additional interfaces
- Visibility - Option types are visible in documentation
来源:Uber Go Style Guide
另一种实现方式是使用闭包:
go
// Closure approach (not recommended)
type Option func(*options)
func WithCache(c bool) Option {
return func(o *options) { o.cache = c }
}推荐使用接口实现方式的原因:
- 可测试性 - 选项可在测试和模拟中直接比较
- 可调试性 - 选项可实现接口以支持打印
fmt.Stringer - 灵活性 - 选项可实现额外的接口
- 可见性 - 选项类型会在文档中清晰展示
Quick Reference
快速参考模板
go
// 1. Unexported options struct with defaults
type options struct {
field1 Type1
field2 Type2
}
// 2. Exported Option interface, unexported method
type Option interface {
apply(*options)
}
// 3. Option type + apply + With* constructor
type field1Option Type1
func (o field1Option) apply(opts *options) { opts.field1 = Type1(o) }
func WithField1(v Type1) Option { return field1Option(v) }
// 4. Constructor applies options over defaults
func New(required string, opts ...Option) (*Thing, error) {
o := options{field1: defaultField1, field2: defaultField2}
for _, opt := range opts {
opt.apply(&o)
}
// ...
}go
// 1. Unexported options struct with defaults
type options struct {
field1 Type1
field2 Type2
}
// 2. Exported Option interface, unexported method
type Option interface {
apply(*options)
}
// 3. Option type + apply + With* constructor
type field1Option Type1
func (o field1Option) apply(opts *options) { opts.field1 = Type1(o) }
func WithField1(v Type1) Option { return field1Option(v) }
// 4. Constructor applies options over defaults
func New(required string, opts ...Option) (*Thing, error) {
o := options{field1: defaultField1, field2: defaultField2}
for _, opt := range opts {
opt.apply(&o)
}
// ...
}Checklist
检查清单
- struct is unexported
options - interface has unexported
Optionmethodapply - Each option has a constructor
With* - Defaults are set before applying options
- Required parameters are separate from
...Option
- 结构体为未导出类型
options - 接口包含未导出的
Option方法apply - 每个配置项都有对应的构造函数
With* - 在应用选项前已设置好默认值
- 必填参数与可变参数分离
...Option
See Also
相关参考
- - Core Go style principles
go-style-core - - Naming conventions for Go
go-naming - - Defensive programming patterns
go-defensive - Self-referential functions and the design of options - Rob Pike
- Functional options for friendly APIs - Dave Cheney
- - Go核心风格原则
go-style-core - - Go命名规范
go-naming - - 防御式编程模式
go-defensive - 自引用函数与选项设计 - Rob Pike
- 友好API的函数选项模式 - Dave Cheney