Go默认参数实现
Functional Options Pattern
在开发过程中,为了避免繁琐的参数设置,难免会需要将函数的一些参数设置为默认值,函数或对象使用这些默认值来保证开箱即用。
然而Go语言本身并不支持默认参数。之前看go-micro
源码时,发现它的代码实现中有一种优雅的默认参数实现方式。搜了一下这种实现方式,发现也并不是go-micro
的首创,而是基于一种go的设计模式:Functional Options Pattern
(函数式选项模式)。恰好最近写代码也用这种模式尝试了下默认参数设置,所以趁此机会总结一下。
Options
为了方便对照,这里还是使用go-micro
中的源码来演示。
首先还是将需要的参数构造为一个结构体Options
:
type Options struct {
Codecs map[string]codec.NewCodec
Broker broker.Broker
Registry registry.Registry
Tracer trace.Tracer
Auth auth.Auth
Transport transport.Transport
Metadata map[string]string
Name string
Address string
Advertise string
Id string
Namespace string
Version string
...
...
...
}
Option
然后定义一个以Options
为参数的函数Option
,方便后续我们对结构体中的参数进行修改。
type Option func(*Options)
func
实现需要指定结构体参数的函数,返回为Option
。
// Server name
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Namespace to register handlers in
func Namespace(n string) Option {
return func(o *Options) {
o.Namespace = n
}
}
// Unique server id
func Id(id string) Option {
return func(o *Options) {
o.Id = id
}
}
// Version of the service
func Version(v string) Option {
return func(o *Options) {
o.Version = v
}
}
newOptions
当我们创建Option时,若指定了结构体中的参数,则使用指定参数,否则使用默认参数。
func newOptions(opt ...Option) Options {
opts := Options{
Codecs: make(map[string]codec.NewCodec),
Metadata: map[string]string{},
RegisterInterval: DefaultRegisterInterval,
RegisterTTL: DefaultRegisterTTL,
}
for _, o := range opt {
o(&opts)
}
...
...
...
if len(opts.Name) == 0 {
opts.Name = DefaultName
}
if len(opts.Id) == 0 {
opts.Id = DefaultId
}
if len(opts.Version) == 0 {
opts.Version = DefaultVersion
}
return opts
}
在这段代码:
for _, o := range opt {
o(&opts)
}
中,通过一系列之前构建的以Options
结构为参数的函数,来实现对Option
的参数指定。可以说,newOptions
函数中除了这段for
循环代码外,其他都是对Options
结构的默认参数设置。
Server
让我们来对上面实现的参数设置进行调用。
func newRpcServer(opts ...Option) Server {
options := newOptions(opts...)
...
...
...
return &rpcServer{
opts: options,
...
...
...
}
}
调用:
defaultServer := newRpcServer() // 全部使用默认配置
nameServer := newRpcServer(Name("Test")) // 指定Server name为Test,其他使用默认配置
mulServer := newRpcServer(Name("Test"), Id("1234)
Version("1.0.1")) // 指定多个参数,其他使用默认配置