Go Micro Options 函數選項模式

函數選項 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
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章