函数选项是一种模式,在这种模式中,您声明一个不透明的 Option
类型,该类型在某些内部结构中记录信息。您接受这些选项的可变数量,并根据内部结构上的选项记录的完整信息进行操作。
在构造函数和其他您认为需要扩展的公共api中,使用此模式的可选参数,特别是当您已经在这些函数上有三个或更多的参数时。
Bad
// package db
func Open(
addr string,
cache bool,
logger *zap.Logger
) (*Connection, error) {
// ...
}
//cache和logger 参数必须始终提供,即使用户希望使用默认值
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)
Good
// package db
//定义一个选项接口
type Option interface {
// ...
}
//返回Cache选项
func WithCache(c bool) Option {
// ...
}
//返回Logger选项
func WithLogger(log *zap.Logger) Option {
// ...
}
// Open creates a connection.
func Open(
addr string,
opts ...Option, //使用选项参数,可变参数
) (*Connection, error) {
// ...
}
//只有在需要时才提供选项
db.Open(addr)
db.Open(addr, db.WithLogger(log))
db.Open(addr, db.WithCache(false))
db.Open(
addr,
db.WithCache(false),
db.WithLogger(log),
)
我们建议的实现此模式的方法是:使用一个Option
接口,并且该接口持有一个未导出的方法,在一个未导出的options
结构体上记录相关选项:
//在未导出的 结构体 options 上记录相关选项
type options struct {
cache bool
logger *zap.Logger
}
//提供一个Option接口
type Option interface {
//提供一个未导出的方法操作相关选项
apply(*options)
}
type cacheOption bool
func (c cacheOption) apply(opts *options) {
opts.cache = bool(c)
}
func WithCache(c bool) Option {
return cacheOption(c)
}
type loggerOption struct {
Log *zap.Logger
}
func (l loggerOption) apply(opts *options) {
opts.logger = l.Log
}
func WithLogger(log *zap.Logger) Option {
return loggerOption{Log: log}
}
// Open creates a connection.
func Open(
addr string,
opts ...Option,
) (*Connection, error) {
options := options{
cache: defaultCache,
logger: zap.NewNop(),
}
for _, o := range opts {
o.apply(&options)
}
// ...
}
注意,有一种方法可以用闭包实现这个模式,但是我们相信上面的模式为作者提供了更多的灵活性,并且更容易为用户调试和测试。它允许在测试和模拟中相互比较选项,而在闭包中则不可能这样做。而且,它允许选项options 实现其他接口,包括fmt.Stringer
允许用户以可读的字符串形式来表示选项options。
另外,请参阅: