函数选项 Functimional Options
在 Go 语言中是没有默认函数的,但是我们可以使用函数选项模式来优雅的解决这个问题。函数选项模式不仅仅可以解决默认函数的问题还可以解决大量参数造成的代码复杂的问题。使用这个模式的有点:
- 支持默认参数:不必像结构体参数那样
- 代码简介:即使想 go-micro 中 像 Broker Cmd Client Server Registry 和 BefroeStart 等等都可以优雅的传入。
- 扩展性好:如果有新增的参数,可以少量代码打到效果。
函数选项模式在 Go 中的应用
1. 先看几个模式使用的实力对象
Option 封装了一个函数 函数接受一个 Options 的指针参数
Options 是具体的 Micro 的具体选项实体
// Option 封装了一个函数
type Option func(*Options)
// 函数选项的具体对象
// 保存了注册 客户端 服务以及 服务开始的方法列表 服务开启之后的方法列表等等
type Options struct {
Broker broker.Broker
Cmd cmd.Cmd
Client client.Client
Server server.Server
Registry registry.Registry
Transport transport.Transport
// Before and After funcs
BeforeStart []func() error
BeforeStop []func() error
AfterStart []func() error
AfterStop []func() error
// Other options for implementations of the interface
// can be stored in a context
Context context.Context
}
2. Micro 如何使用函数选项
NewService 方法调用了内部方法 newService
newServices 调用了内部方法 newOptions
newOptions
1. 方法先给了默认的实现方式
2. 循环传入的参数 Options 函数 执行方法 传入 opt 指针对象
3. 最终返回 services 对象
func NewService(opts ...Option) Service {return newService(opts...)
}
func newService(opts ...Option) Service {options := newOptions(opts...)
options.Client = &clientWrapper{
options.Client,
metadata.Metadata{HeaderPrefix + "From-Service": options.Server.Options().Name,
},
}
return &service{opts: options,}
}
func newOptions(opts ...Option) Options {
opt := Options{
Broker: broker.DefaultBroker,
Cmd: cmd.DefaultCmd,
Client: client.DefaultClient,
Server: server.DefaultServer,
Registry: registry.DefaultRegistry,
Transport: transport.DefaultTransport,
Context: context.Background(),}
for _, o := range opts {o(&opt)
}
return opt
}
例子
可以看到 Name 函数 接受一个 string 返回一个 Option
函数内部接受一个 Options 指针参数 内部给 server 复制了那么属性
剩下的 RegisterTTL 给 server 对象复制了 time to live(生存时间)
RegisterInterval 函数设置了 server 的注册间隔时间
可以看到 想 Micro 框架这么复杂的对象和这么多的设置,在不能使用默认参数的情况下,使用了函数选项模式,很优雅的实现了功能同事代码也很清楚和优雅。
service := micro.NewService(micro.Name("hellooo"),
micro.RegisterTTL(time.Second*30),
micro.RegisterInterval(time.Second*10), // 服务名称
)
func Name(n string) Option {return func(o *Options) {o.Server.Init(server.Name(n))
}
}
func RegisterTTL(t time.Duration) Option {return func(o *Options) {o.Server.Init(server.RegisterTTL(t))
}
}
func RegisterInterval(t time.Duration) Option {return func(o *Options) {o.Server.Init(server.RegisterInterval(t))
}
}
简单模拟实现
如果光看例子不是很清楚的话,我们可以自己实践一下。做一个最简单的例子:
package main
import (
"fmt"
"time"
)
func main() {
s := NewServices(SetName("peter"),
SetTimeout(time.Second*5),
)
fmt.Println("name:", s.conf.Name)
fmt.Println("time", s.conf.Timeout)
}
type Option func(options *Config)
type Config struct {
Name string
Timeout time.Duration
}
type Services struct {conf Config}
func SetTimeout(t time.Duration) Option {return func(options *Config) {options.Timeout = t}
}
func SetName(name string) Option {return func(options *Config) {options.Name = name}
}
func NewServices(opts ...Option) Services {c := Config{}
for _, op := range opts {op(&c)
}
s := Services{}
s.conf = c
return s
}