go-micro 框架源碼剖析 之 函數選項模式

近期在查閱go-micro源碼過程當中發現,在設置建立微服務的參數選項時都依賴於項目外層一個名爲 github.com/micro/go-micro/options.go的文件,它定義了建立一個微服務所須要的各類參數選項,其實現過程當中使用的方法值得借鑑。

一、建立微服務示例

go-micro 框架初探篇,在go-micro中使用micro.NewService建立一個微服務html

import "github.com/micro/go-micro"

service := micro.NewService()

也能夠在建立過程當中設置服務選項(如服務名稱,自定義服務發現等)git

service := micro.NewService(
        micro.Name("hellooo"),
        micro.Version("latest"),
)

對比兩個例子能夠發現,雖然go語言不支持默認參數,可是上面的代碼例子卻達到了一樣的效果。github

二、如何實現默認參數

那麼,go-micro是如何實現默認參數的呢?進入micro.NewService源碼看個究竟:
github.com/micro/go-micro/go-micro.gosegmentfault

// Option定義爲一個方法,接受Options類型的指針參數
// 注意這個Option類型(函數類型),這是理解本文的關鍵
type Option func(*Options)
...
// 建立並返回一個服務:接收Option函數類型的不定向參數列表
func NewService(opts ...Option) Service {
    return newService(opts...)
}

...

// 內部真正建立服務的方法
func newService(opts ...Option) Service {
    // 關鍵步驟:利用opts函數列表初始化服務
    options := newOptions(opts...)

     ...

    // 返回初始化的服務
    return &service{
        opts: options,
    }
}

其中newOptions方法最爲關鍵,繼續跟蹤下去:
github.com/micro/go-micro/options.go,該文件定義了設置服務相關的全部選項:api

// 服務選項結構體
// 由於go-micro是可插件化的框架,其中的組件(如消息中間件)均是能夠用其餘相似服務替換的
type Options struct {
    Broker    broker.Broker 
    Cmd       cmd.Cmd
    Client    client.Client
    Server    server.Server
    Registry  registry.Registry
    Transport transport.Transport

    // Register loop interval
    RegisterInterval time.Duration

    // Before and After funcs
    BeforeStart []func() error
    BeforeStop  []func() error
    AfterStart  []func() error
    AfterStop   []func() error

    Context context.Context
}

// 生成服務相關選項(初始化使用go-micro框架定義的默認組件)
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) // 依次調用opts函數列表中的函數,爲服務選項(opt變量)賦值
    }

    return opt
}

...

// 服務名字選項
// 返回一個Option類型的函數(閉包):接受Options類型指針參數並修改之
func Name(n string) Option {
    return func(o *Options) {
        o.Server.Init(server.Name(n))
    }
}

// 服務版本選項
// 返回一個Option類型的函數(閉包):接受Options類型指針參數並修改之
func Version(v string) Option {
    return func(o *Options) {
        o.Server.Init(server.Version(v))
    }
}

...

除了Name與Version選項以外,go-micro的Broker、Register、Transport等選項均是經過此方法實現的。它極大地利用了go語言支持閉包的特性,優雅地實現了函數支持默認參數
的功能。
再次回到文章開頭出建立服務的代碼就很好理解了:閉包

// 建立服務,同時接收對指定參數選項的設置
service := micro.NewService(
        micro.Name("hellooo"),
        micro.Version("latest"),
)

這樣的寫法處理調用代碼比較簡潔以外,它擴展性特別好,增長新的選項時,只須要不多量的代碼。框架

這即是go語言的函數選項模式。函數

三、函數選項(Functional Options)模式

函數選項模式是由Rob Pike提出,並由Dave Cheney等推廣開,它優雅地解決了go語言中默認參數問題。微服務

雖然Functional Options並非一個新的概念,從Rob Pike提出至今已4年有餘(2014),但做爲Golang新手,仍是很受啓發。這也是閱讀源碼帶來的好處。oop

下面總結一下,函數選項模式有哪些優勢:

  • 支持默認參數:沒必要向結構體參數那樣,不使用時仍必須參數一個空的struct值
  • 代碼簡潔:即便是像go-micro這種支持如此繁多選項,代碼也很美觀
  • 擴展性好:增長新的選項只需少許代碼

推而廣之:相似結構體中變量的賦值均可以效仿之。

四、 ReFerences

https://commandcenter.blogspo...
https://dave.cheney.net/2014/...
https://halls-of-valhalla.org...
https://lingchao.xin/post/fun...

相關文章
相關標籤/搜索