函数选项 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 mainimport (    "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}