函數選項 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
}