想到设置函数默认值,第一反映是如下的设计思路:
这几个思路可行,但是不够优雅。当所有待设置默认值的字段发生变化时,上面三个思路都要修改源代码,才能适应变化,有没有更好的方法,参看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
中的接口类型(接口中方法的具体实现)