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