Go - options模式(函数式选项模式)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go - options模式(函数式选项模式)相关的知识,希望对你有一定的参考价值。

参考技术A 作为 Golang 开发人员,遇到的众多问题之一是试图将函数的参数设为可选。这是一个非常常见的用例,有一些对象应该使用一些基本的默认设置开箱即用,并且您可能偶尔想要提供一些更详细的配置。

在python 中,你可以给参数一个默认值,并在调用方法时省略它们。但是在 Golang 中,是无法这么做。

那么怎么解决这个问题呢? 答案就是Options模式。Options模式在Golang中应用十分广泛,几乎每个框架中都有他的踪迹。

可以发现,在这种方式中,拓展起来是非常麻烦的,以及设置出默认值十分繁琐。

Go语言函数式选项模式


普通的结构体模式存在的缺点

普通模式需要输入相应的参数,但是有的时候我们有默认参数的,所以普通模式不灵活。

// student结构体
type StudentModel struct {
 name   string
 school string
 age    int
}

// 构建结构体,必须输入对应参数
func NewStudentModel(name, school string, age int) *StudentModel {
 return &StudentModel{
  name:   name,
  school: school,
  age:    age,
 }
}

func NewStudentModel2(school string, age int) *StudentModel {
 return &StudentModel{
  // 这样子未免太蹩脚了
  name:   "会打篮球的程序猿",
  school: school,
  age:    age,
 }
}

// 构建结构体,这种方式不能修改默认属性,不够灵活
func NewStudentDefault() *Student {
 return &Student{
  name:   "lzhpo",
  school: "北京大学",
  age:    20,
 }
}

函数式选项模式运用到的知识点?

闭包(闭包 = 匿名函数 + 引用环境):一个持有外部环境变量的函数就是闭包。

函数式选项模式的优点?

可以根据需要,灵活的设置结构体的值。

Go 语言函数式选项模式-示例 Demo

basketball.go

package structs

// 定义一个篮球结构体
type BasketBall struct {
 Name string
 Color string
 Price float64
}

// 针对篮球结构体定义一个函数类型
type BasketBallFunc func(*BasketBall)

func WithName(name string) BasketBallFunc
 {
 return func(basketBall *BasketBall) {
  basketBall.Name = name
 }
}

func WithColor(color string) BasketBallFunc {
 return func(basketBall *BasketBall) {
  basketBall.Color = color
 }
}

func WithPrice(price float64) BasketBallFunc {
 return func(basketBall *BasketBall) {
  basketBall.Price = price
 }
}

// 结构体默认实例
var (
 basketBallDefault = BasketBall{
  Name: "斯伯丁比赛专用球",
  Color: "黄色",
  Price: 300.99,
 }
)

// 创建一个构造函数,传入的是我定义的函数类型
func NewBasketBall(opts ...BasketBallFunc) *BasketBall {
 // 默认值,这里也可以抽一个默认值的实例,直接引用也可以
 //temp := BasketBall{
 // name: "斯伯丁比赛专用球",
 // color: "黄色",
 // price: 300.99,
 //}

 temp := basketBallDefault

 // 遍历,并对每一项应用返回的闭包到结构体的选项变量
 for _, opt := range opts {
  opt(&temp)
 }

 return &temp
}

main.go

package main

import (
 "fmt"
 "study-golang/function-model/model-demo3/structs"
)

/*
Go语言函数式选项模式:

为什么要使用函数式选项模式?
- 普通模式需要输入相应的参数,但是有的时候我们有默认参数的,所以普通模式不灵活。

函数式选项模式运用到的知识点?
- 闭包(闭包 = 匿名函数 + 引用环境):一个持有外部环境变量的函数就是闭包。

函数式选项模式的优点?
- 可以根据需要,灵活的设置结构体的值。

函数式选项模式设计参考来源,go-micro源码:https://github.com/micro/go-micro/blob/master/options.go
 */

func main() {
 fmt.Println(structs.NewBasketBall())

 fmt.Println(structs.NewBasketBall(structs.WithName("李宁比赛专用球")))

 fmt.Println(structs.NewBasketBall(
  structs.WithName("李宁比赛专用球"), structs.WithColor("白色")))

 fmt.Println(structs.NewBasketBall(
  structs.WithName("李宁比赛专用球"), structs.WithColor("白色"), structs.WithPrice(129.99)))
}

运行结果:

&{斯伯丁比赛专用球 黄色 300.99}
&{李宁比赛专用球 黄色 300.99}
&{李宁比赛专用球 白色 300.99}
&{李宁比赛专用球 白色 129.99}

函数式选项模式应用-go-micro 源码

https://github.com/micro/go-micro/blob/master/options.go

里面就用到了函数式选项模式

options.go源代码解析:

go-micro里面的options的函数式选择模式.png
package micro

import (
 "context"
 "go-micro/go-micro-master/config/source/cli"
 "time"

 "github.com/micro/cli/v2"
 "github.com/micro/go-micro/v2/auth"
 "github.com/micro/go-micro/v2/broker"
 "github.com/micro/go-micro/v2/client"
 "github.com/micro/go-micro/v2/config"
 "github.com/micro/go-micro/v2/config/cmd"
 "github.com/micro/go-micro/v2/debug/profile"
 "github.com/micro/go-micro/v2/debug/trace"
 "github.com/micro/go-micro/v2/registry"
 "github.com/micro/go-micro/v2/router"
 "github.com/micro/go-micro/v2/runtime"
 "github.com/micro/go-micro/v2/selector"
 "github.com/micro/go-micro/v2/server"
 "github.com/micro/go-micro/v2/store"
 "github.com/micro/go-micro/v2/transport"
)

/*

【函数式选项设计模式】

 */


// 服务注册结构体
// Options for micro service
type Options struct {
 Auth      auth.Auth
 Broker    broker.Broker
 Cmd       cmd.Cmd
 Config    config.Config
 Client    client.Client
 Server    server.Server
 Store     store.Store
 Registry  registry.Registry
 Router    router.Router
 Runtime   runtime.Runtime
 Transport transport.Transport
 Profile   profile.Profile

 // 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

 Signal bool
}

// 创建newOptions构造函数,传入的是定义的函数类型 type Option func(*Options)  在micro.go里面
func newOptions(opts ...Option) Options
 {
 // 默认结构体属性值
 opt := Options{
  Auth:      auth.DefaultAuth,
  Broker:    broker.DefaultBroker,
  Cmd:       cmd.DefaultCmd,
  Config:    config.DefaultConfig,
  Client:    client.DefaultClient,
  Server:    server.DefaultServer,
  Store:     store.DefaultStore,
  Registry:  registry.DefaultRegistry,
  Router:    router.DefaultRouter,
  Runtime:   runtime.DefaultRuntime,
  Transport: transport.DefaultTransport,
  Context:   context.Background(),
  Signal:    true,
 }

 // 遍历,并对每一项应用返回的闭包到结构体的选项变量
 for _, o := range opts {
  o(&opt)
 }

 return opt
}

// 设置Broker
// Broker to be used for service
func Broker(b broker.Broker) Option {
 // 闭包,进入的是一个外部变量,返回的是一个匿名函数
 return func(o *Options) {
  o.Broker = b
  // Update Client and Server
  o.Client.Init(client.Broker(b))
  o.Server.Init(server.Broker(b))
 }
}

// 。。。。下面都是一样的道理

// 设置Cmd
func Cmd(c cmd.Cmd) Option {
 return func(o *Options) {
  o.Cmd = c
 }
}

// 设置Client。。。。。下面一大堆也是一样的道理
// Client to be used for service
func Client(c client.Client) Option {
 return func(o *Options) {
  o.Client = c
 }
}

// Context specifies a context for the service.
// Can be used to signal shutdown of the service and for extra option values.
func Context(ctx context.Context) Option {
 return func(o *Options) {
  o.Context = ctx
 }
}

// HandleSignal toggles automatic installation of the signal handler that
// traps TERM, INT, and QUIT.  Users of this feature to disable the signal
// handler, should control liveness of the service through the context.
func HandleSignal(b bool) Option {
 return func(o *Options) {
  o.Signal = b
 }
}

// Profile to be used for debug profile
func Profile(p profile.Profile) Option {
 return func(o *Options) {
  o.Profile = p
 }
}

// Server to be used for service
func Server(s server.Server) Option {
 return func(o *Options) {
  o.Server = s
 }
}

// Store sets the store to use
func Store(s store.Store) Option {
 return func(o *Options) {
  o.Store = s
 }
}

// Registry sets the registry for the service
// and the underlying components
func Registry(r registry.Registry) Option {
 return func(o *Options) {
  o.Registry = r
  // Update router
  o.Router.Init(router.Registry(r))
  // Update server
  o.Server.Init(server.Registry(r))
  // Update Broker
  o.Broker.Init(broker.Registry(r))
 }
}

// Tracer sets the tracer for the service
func Tracer(t trace.Tracer) Option {
 return func(o *Options) {
  o.Server.Init(server.Tracer(t))
 }
}

// Auth sets the auth for the service
func Auth(a auth.Auth) Option {
 return func(o *Options) {
  o.Auth = a
  o.Server.Init(server.Auth(a))
 }
}

// Config sets the config for the service
func Config(c config.Config) Option {
 return func(o *Options) {
  o.Config = c
 }
}

// Selector sets the selector for the service client
func Selector(s selector.Selector) Option {
 return func(o *Options) {
  o.Client.Init(client.Selector(s))
 }
}

// Transport sets the transport for the service
// and the underlying components
func Transport(t transport.Transport) Option {
 return func(o *Options) {
  o.Transport = t
  // Update Client and Server
  o.Client.Init(client.Transport(t))
  o.Server.Init(server.Transport(t))
 }
}

// Runtime sets the runtime
func Runtime(r runtime.Runtime) Option {
 return func(o *Options) {
  o.Runtime = r
 }
}

// Router sets the router
func Router(r router.Router) Option {
 return func(o *Options) {
  o.Router = r
  // Update client
  o.Client.Init(client.Router(r))
 }
}

// Convenience options

// Address sets the address of the server
func Address(addr string) Option {
 return func(o *Options) {
  o.Server.Init(server.Address(addr))
 }
}

// Name of the service
func Name(n string) Option {
 return func(o *Options) {
  o.Server.Init(server.Name(n))
 }
}

// Version of the service
func Version(v string) Option {
 return func(o *Options) {
  o.Server.Init(server.Version(v))
 }
}

// Metadata associated with the service
func Metadata(md map[string]string) Option {
 return func(o *Options) {
  o.Server.Init(server.Metadata(md))
 }
}

// Flags that can be passed to service
func Flags(flags ...cli.Flag) Option {
 return func(o *Options) {
  o.Cmd.App().Flags = append(o.Cmd.App().Flags, flags...)
 }
}

// Action can be used to parse user provided cli options
func Action(a func(*cli.Context) errorOption {
 return func(o *Options) {
  o.Cmd.App().Action = a
 }
}

// RegisterTTL specifies the TTL to use when registering the service
func RegisterTTL(t time.Duration) Option {
 return func(o *Options) {
  o.Server.Init(server.RegisterTTL(t))
 }
}

// RegisterInterval specifies the interval on which to re-register
func RegisterInterval(t time.Duration) Option {
 return func(o *Options) {
  o.Server.Init(server.RegisterInterval(t))
 }
}

// WrapClient is a convenience method for wrapping a Client with
// some middleware component. A list of wrappers can be provided.
// Wrappers are applied in reverse order so the last is executed first.
func WrapClient(w ...client.Wrapper) Option {
 return func(o *Options) {
  // apply in reverse
  for i := len(w); i > 0; i-- {
   o.Client = w[i-1](o.Client)
  }
 }
}

// WrapCall is a convenience method for wrapping a Client CallFunc
func WrapCall(w ...client.CallWrapper) Option {
 return func(o *Options) {
  o.Client.Init(client.WrapCall(w...))
 }
}

// WrapHandler adds a handler Wrapper to a list of options passed into the server
func WrapHandler(w ...server.HandlerWrapper) Option {
 return func(o *Options) {
  var wrappers []server.Option

  for _, wrap := range w {
   wrappers = append(wrappers, server.WrapHandler(wrap))
  }

  // Init once
  o.Server.Init(wrappers...)
 }
}

// WrapSubscriber adds a subscriber Wrapper to a list of options passed into the server
func WrapSubscriber(w ...server.SubscriberWrapper) Option {
 return func(o *Options) {
  var wrappers []server.Option

  for _, wrap := range w {
   wrappers = append(wrappers, server.WrapSubscriber(wrap))
  }

  // Init once
  o.Server.Init(wrappers...)
 }
}

// Before and Afters

// BeforeStart run funcs before service starts
func BeforeStart(fn func() errorOption {
 return func(o *Options) {
  o.BeforeStart = append(o.BeforeStart, fn)
 }
}

// BeforeStop run funcs before service stops
func BeforeStop(fn func() errorOption {
 return func(o *Options) {
  o.BeforeStop = append(o.BeforeStop, fn)
 }
}

// AfterStart run funcs after service starts
func AfterStart(fn func() errorOption {
 return func(o *Options) {
  o.AfterStart = append(o.AfterStart, fn)
 }
}

// AfterStop run funcs after service stops
func AfterStop(fn func() errorOption {
 return func(o *Options) {
  o.AfterStop = append(o.AfterStop, fn)
 }
}


以上是关于Go - options模式(函数式选项模式)的主要内容,如果未能解决你的问题,请参考以下文章

go 的选项模式

Go语言函数式选项模式

Go语言实践模式 - 函数选项模式(Functional Options Pattern)

Golang函数式选项(Functional Options)模式

Golang函数式选项(Functional Options)模式

Go编程模式 - 5.函数式选项