13 函数设置默认值

想到设置函数默认值,第一反映是如下的设计思路:

  1. 提供一个初始化函数,所有待设置默认值的字段都做为参数,如果不需要的时候传该类型的零值(把复杂度暴露给函数调用者
  2. 所有待设置默认值的字段封装为结构体做为初始化函数中的一个参数(把复杂度暴露给函数调用者
  3. 提供多个初始化函数,针对每个场景都进行内部默认值设置

这几个思路可行,但是不够优雅。当所有待设置默认值的字段发生变化时,上面三个思路都要修改源代码,才能适应变化,有没有更好的方法,参看gRPC的实现。

把gRPC中函数设置默认值部分单独拿出来看一下:

package main

import (
 "context"
 "time"
)

// 待设置默认值的字段,封装为一个内部结构体
type dialOptions struct {
 insecure bool
 timeout  time.Duration
}

// 用户需要创建的客户端连接,其中包含上述待设置默认值的字段
type ClientConn struct {
 ctx       context.Context
 authority string
 dopts     dialOptions
}


// 创建一个接口把所有待设置默认值的字段都封装起来
type DialOption interface {
 apply(options *dialOptions)
}

// 待设置默认值字段的初始默认值
func defaultDialOptions() dialOptions {
 return dialOptions{
  insecure: false,
  timeout:  0,
 }
}

// 用于创建客户端连接的函数,其中包含待设置默认值的字段
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
 cc := &ClientConn{
  ctx:       ctx,
  authority: target,
  dopts:     defaultDialOptions(),
  csMgr:     &connectivityStateManager{},
 }

 for _, opt := range opts {
  opt.apply(&cc.dopts)
 }
 return cc, nil
}

// 实现DialOption接口
type EmptyDialOption struct{}

func (e EmptyDialOption) apply(options *dialOptions) {}

// DialOption接口的具体实现
// 重点1:结构体中的函数对象的参数是待设置默认值的字段组成的结构体
type funcDialOption struct {
 do func(options *dialOptions)
}

// 实现DialOption接口的apply方法
func (f *funcDialOption) apply(options *dialOptions) {
 f.do(options)
}

// 创建一个funcDialOption结构体,传入一个dialOptions为参数的匿名函数
func newFuncDialOption(do func(options *dialOptions)) *funcDialOption {
 return &funcDialOption{do: do}
}

// 暴露给用户,用于修改待设置默认值字段的值
func WithInsecure() DialOption {
 return newFuncDialOption(func(options *dialOptions) {
  options.insecure = true
 })
}

func WithTime(duration time.Duration) DialOption {
 return newFuncDialOption(func(options *dialOptions) {
  options.timeout = duration
 })
}

// 用户调用过程
func main() {
 opts := []DialOption{
  WithInsecure(),   //返回的funcDialOption对象实现了DialOption接口
  WithTime(1000),
 }

 DialContext(context.Background(), "", opts...)
}

总结一下:

  1. 把待设置默认值的字段封装在一个结构体中,并且将字段都私有化
  2. 定义一个接口类型,这个接口提供一个方法,该方法的参数是1中封装的结构体的指针类型(用于修改结构体的内部值
  3. 定义一个函数类型,该函数类型与接口类型中方法拥有一样的参数(划重点),在上面发gRPC中没有创建函数类型而是直接使用了匿名函数
  4. 定义一个结构体,并且实现2中的接口类型(接口中方法的具体实现
  5. 创建函数(使用with+字段名)的命名方式,封装待设置默认值的字段的修改方法
上次修改: 2 July 2020