Go-Micro-Options-函数选项模式

3次阅读

共计 2657 个字符,预计需要花费 7 分钟才能阅读完成。

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

正文完
 0