go使用options模式设置参数

CKeenGolanggo语言基础约 1119 字大约 4 分钟

作者:程序员CKeen
博客:http://ckeen.cnopen in new window

长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码作为长期兴趣爱好😄


Functional Options Pattern(函数式选项模式)可用于传递不同选项配置到方法中。当每次选项参数有变更的时候,可以不改变方法或者接口的参数,这样保证了接口兼容性。使用Options的模式也更利于程序的扩展,所以在众多包中得到了广泛的使用。

下面我来具体讲解一下Options的使用方法:

1.  使用函数传递传递参数

一般我们需要创建一个服务的客户端,可能是这样的写的

type Client struct {
   url      string
   username string
   password string
}

func NewClient(url string, username string, password string,db string) *Client {
   return &Client{
      url:      url,
      username: username,
      password: password
   }
}

这么写的时候,参数都是通过New方法进行传入的。后来因为需求,又需要添加如下可选参数

readTimeout time.Duration    // 读取的超时时间
charset string               // 可以配置编码

如果按参数传递的话,我们就得修改New方法的参数了,如果有多个项目依赖这个包的话,那么引用的地方都得修改相应的New方法参数。那么怎么很好的解决这个问题,这个时候Options模型就出现了。

2. 使用options选项模式

我们首先将上述的可选参数以options方式进行定义(这里options为小写,是不对对外暴露出来具体的配置项)


type options struct { 
    readTimeout time.Duration 
    charset string 
}

然后我们定义Option的结构

type Option func(*options)

接着我们再定义配置options中参数的公开方法,用来设置参数

func WithReadTimeout(timeout time.Duration) Option {
   return func(o *options) {
      o.readTimeout = timeout
   }
}

func WithCharset(charset string) Option {
   return func(o *options) {
      o.charset = charset
   }
}

同时将options添加到Client中,并在New方法中添加相应的处理

type Client struct {
   url      string
   username string
   password string
   opts   options
}

func NewClient(url string, username string, password string,opts ...Option) *Client {
   // 创建一个默认的options
   op := options{
      readTimeout: 10,
      charset: "utf8",
   }
   // 调用动态传入的参数进行设置值
   for _, option := range opts {
      option(&op)
   }
   return &Client{
      url:      url,
      username: username,
      password: password,
      opts:    op,
   }
}

使用Options的模式后,添加参数就不需要再去修改New方法了。 添加参数我们只需要在options中添加对应的属性即可,同时配置相应的对外的WithXXX方法即可。 于是我们调用就可以使用如下方式:

client := NewClient("localhost:3333","test","test",WithReadTimeout(100),WithCharset("gbk"))

后面的可以选参数可以传入任意多个Option了。这里配置的Option参数多了后,导致配置过长,我们进一步把他封装到函数中

func Options() []Option{
    return []{
        WithReadTimeout(100),
        WithCharset("gbk")
    }
}

client := NewClient("localhost:3333","test","test",Options()...)

到这里options选项模型就可以很方便的传递可选参数了,同时也不需要添加一个参数就去添加方法的参数。

3.开源库options的用法参考

  • gRPC中也大量使用使用options选项模式传递参数。 下面是Dial方法使用options选项模式
type dialOptions struct {
   unaryInt  UnaryClientInterceptor
   streamInt StreamClientInterceptor

   chainUnaryInts  []UnaryClientInterceptor
   chainStreamInts []StreamClientInterceptor

   cp              Compressor
   dc              Decompressor
   bs              internalbackoff.Strategy
   block           bool
   returnLastError bool
   timeout         time.Duration
   scChan          <-chan ServiceConfig
   authority       string
   copts           transport.ConnectOptions
   callOptions     []CallOption
   balancerBuilder             balancer.Builder
   channelzParentID            int64
   disableServiceConfig        bool
   disableRetry                bool
   disableHealthCheck          bool
   healthCheckFunc             internal.HealthChecker
   minConnectTimeout           func() time.Duration
   defaultServiceConfig        *ServiceConfig // defaultServiceConfig is parsed from defaultServiceConfigRawJSON.
   defaultServiceConfigRawJSON *string
   resolvers                   []resolver.Builder
}

// options处理的交给实现接口类
type DialOption interface {
   apply(*dialOptions)
}

func Dial(target string, opts ...DialOption) (*ClientConn, error) {
   return DialContext(context.Background(), target, opts...)
}

---------- 具体实现apply的实现 ----------------
type funcDialOption struct {
   f func(*dialOptions)
}

func (fdo *funcDialOption) apply(do *dialOptions) {
   fdo.f(do)
}

func WithTimeout(d time.Duration) DialOption {
   return newFuncDialOption(func(o *dialOptions) {
      o.timeout = d
   })
}

上面我可以看到grpc的dial方法是另外一种实现options的方式,同时dialOptions里面还封装了其他的options,其中比较有意思是callOptions,它是一个CallOption类型数组,而CallOption是一个interface的类型,如下:

type CallOption interface {

	before(*callInfo) error

	after(*callInfo, *csAttempt)
}

这个有点像AOP一样的,为call调用之前和之后提供了前置和后置的回调。

  • 还有如下库中大量使用options的模式 ​
  1. bilibili开源框架kratos: https://github.com/go-kratos/kratos/blob/main/options.goopen in new window

  2. beego框架封装的httpclient: beego/httpclient.go at develop · beego/beego · GitHub

  3. etc...