设计模式:Options Pattern In Golang
下面将记录函数可选(functional options)模式在golang中的实现。
函数可选参数模式(或者可选参数模式、Optional Parameters Pattern),用在当构造函数和公共函数API需要可选参数,特别时当具有三个或者更多可选参数时。
这个模式的优势在于,可以实现一个方法,并用下面的简单方式进行调用,如:
obj.Method(mandatory1, mandatory2)或者向下面这样,通过是用可选的参数,来改变它的行为:
obj.Method(mandatory1, mandatory2, option1, option2, option3)这可以避免为可选参数,使用笨重的零值参数:
obj.Method(mandatory1, mandatory2, nil, "", 0)或者使用同样笨重的使用配置对象的方式:
cfg := &ConfigForMethod{ Optional1: ..., Optional2: ..., Optional3: ...,}obj.Method(mandatory1, mandatory2, &cfg)1. 通过接口实现
Section titled “1. 通过接口实现”使用一个Option接口,该接口保存一个未导出的方法,同时在一个未导出options结构中记录可选的参数信息。
package db
type options struct { cache bool limit int logger *zap.Logger}
type Option interface { apply(*options)}
//cachetype cacheOption bool
func (c cacheOption)apply(opts *options){ opts.cache = bool(c)}
func WithCache(c bool)Option{ return cacheOption(c)}
//limittype limitOption int
func (l limitOption)apply(opts *options){ opts.limit = int(l)}
func WithLimit(limit int)Option{ return limitOption(limit)}
//loggertype loggerOption struct { logger *zap.Logger}
func (l loggerOption)apply(opts *options){ opts.logger = l.logger}
func WithLogger(log *zap.Logger) Option{ return loggerOption{logger: log}}函数:
package db
func Open(addr string, opts ...Option)(*Connection, error){ //默认值 options := options { cache: 1, limit: 1, logger: zap.NewNop(), }
for _, o := range opts { o.apply(options) }
//...}使用:
db.Open(addr)db.Open(add, db.WithLimit(10))db.Open(addr, db.WithLogger(log))db.Open(addr, db.WithCache(1), db.WithLimit(10), db.WithLogger(log))2. 通过闭包实现
Section titled “2. 通过闭包实现”将Option定义为函数类型,使用闭包来实现
package db
type Option func(*options)
type options struct { cache bool limit int logger *zap.Logger}
func WitchCache(cache bool)Option{ return func(opts *options){ opts.cache = cache }}
func WithLimit(limit int) Option { return func(opts *options){ opts.limit = limit }}
func WithLogger(log *zap.Logger) Option { return func(opts *options) { opts.logger = log }}函数:
package db
func Open(addr string, opts ...Option)(*Connection, error){ //默认值 options := options { cache: 1, limit: 1, logger: zap.NewNop(), }
for _, o := range opts { o(options) }
//...}使用
db.Open(addr)db.Open(add, db.WithLimit(10))db.Open(addr, db.WithLogger(log))db.Open(addr, db.WithCache(1), db.WithLimit(10), db.WithLogger(log))3. 接口+闭包
Section titled “3. 接口+闭包”type dailOption struct { disableRetry bool}
type DailOption interface { apply(*dailOption)}
type funcDialOption struct { f func(*dailOption)}
func (fdo *funcDialOption) apply(do *dailOption) { fdo.f(do)}
func newFuncDialOption(f func(o *dailOption)) *funcDialOption { return &funcDialOption{f: f}}
func WithDisableRetry() DailOption { return newFuncDialOption(func(o *dailOption) { o.disableRetry = true })}函数:
// Dial creates a client connection to the given target.func Dial(target string, opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...)}使用:
grpc.Dial("localhost:8080")grpc.Dial("localhost:8080", grpc.WithInsecure(), grpc.WithBlock())grpc.Dial("localhost:8080", grpc.WithDisableRetry())方式3中,需要将具体的配置选项暴露出来,而这种方式不用。
4. 通用方式实现
Section titled “4. 通用方式实现”通用方式实现一个可复用组件,来实现一个具有下面形式参数的函数:
obj.Method(mandatory1, mandatory2, option1, option2, option3)在内部,只需要像下面这样声明定义该方法:
func (obj *Object)Method(m1 Type1, m2 Type2, options ...Option) { ....}Option对象有两个部分,一个标识和一个值。标识和值都被声明成interface{},这样标识和值都可以是任意的数据类型。对于标识,通常最好使用一个未导出的空结构,如:
// Interface defines the minimum interface that an option must fulfilltype Option interface { // Ident returns the "indentity" of this option, a unique identifier that // can be used to differentiate between options Ident() interface{}
// Value returns the corresponding value. Value() interface{}}
type pair struct { ident interface{} value interface{}}
// New creates a new Optionfunc New(ident, value interface{}) Option { return &pair{ ident: ident, value: value, }}
func (p *pair) Ident() interface{} { return p.ident}
func (p *pair) Value() interface{} { return p.value}type identOptionalParamOne struct{}type identOptionalParamTwo struct{}type identOptionalParamThree struct{}
func WithOptionOne(v ...) Option { return option.New(identOptionalParamOne{}, v)}然后,可以通过下面的方式,调用上面定义的Method方法:
obj.Method(m1, m2, WithOptionOne(...), WithOptionTwo(...), WithOptionThree(...))同时Method的options参数,需要用类似下面的方式进行解析:
func (obj *Object) Method(m1 Type1, m2 Type2, options ...Option) { paramOne := defaultValueParamOne for _, option := range options { switch option.Ident() { case identOptionalParamOne{}: paramOne = option.Value().(...) } } ...}5 参考资料
Section titled “5 参考资料”- https://github.com/bytedaring/uber_go_guide_cn#%E5%8A%9F%E8%83%BD%E9%80%89%E9%A1%B9
- https://github.com/lestrrat-go/option
- https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
- https://github.com/grpc/grpc-go/blob/master/dialoptions.go
- https://github.com/grpc/grpc-go/blob/master/clientconn.go