Go的函数式选项模式

分类: 工作

发现

最近在使用 grpc 的时候,对于 client/server 初始化的配置,是通过传入函数进行对配置的处理的。

conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())

通过传入 grpc.WithInsecure()grpc.WithBlock() 两个方法,启用了 grpc 的 tls 和 block,感觉对配置的可选自定义化非常方便。

继续看这个是怎么实现的。

func Dial(target string, opts ...DialOption) (*ClientConn, error)

这是 client 端新建 conn 的方法,有两个参数target目标地址、opts是一个DialOption数组

type DialOption interface {
	apply(*dialOptions)
}

DialOption 接口有一个 apply 方法。 grpc.WithInsecure()grpc.WithBlock() 都实现了这个接口。

dialOptions 中则是拨号的所有配置, apply 方法就是提供来方便自定义修改参数的。

func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
	//省略代码
	cc.safeConfigSelector.UpdateConfigSelector(&defaultConfigSelector{nil})
	cc.ctx, cc.cancel = context.WithCancel(context.Background())

	for _, opt := range opts {
		opt.apply(&cc.dopts)
	}
	//省略代码
}

沿着 Dial 方法继续看,可以定位到如上。所有传入的 opts 都会按传入的顺序先后调用一次。

模式

查阅资料,这是一种设计模式。

Functional Options Pattern(函数式选项模式),提供了将一个函数的参数设置为可选的功能,也就是说我们可以选择参数中的某几个,并且可以按任意顺序传入参数。

应用场景

  1. 一个函数需要不同的参数;而众所周知,go 目前不支持函数重载,所以就可以使用这种可变长参数的形式来模拟函数重载这种情况。
  2. 一个复杂的结构体,在业务中可能会频繁的添加字段,如果把 NewXXX() 等方法写死,那么后续修改起来非常麻烦。此时使用选项模式,对其初始化,新增一个字段,就新增对应的一个函数来处理字段的初始化,应用程序的扩展性大大的提高了。
  3. 可读性好,给代码的维护带来了便利。

举个例子

var defaultStuffClient = stuffClient{
    retries: 3,
    timeout: 2,
}
type StuffClientOption func(*stuffClient)
func WithRetries(r int) StuffClientOption {
    return func(o *stuffClient) {
        o.retries = r
    }
}
func WithTimeout(t int) StuffClientOption {
    return func(o *stuffClient) {
        o.timeout = t
    }
}
type StuffClient interface {
    DoStuff() error
}
type stuffClient struct {
    conn    Connection
    timeout int
    retries int
}
type Connection struct{}
func NewStuffClient(conn Connection, opts ...StuffClientOption) StuffClient {
    client := defaultStuffClient
    for _, o := range opts {
        o(&client)
    }
    
    client.conn = conn
    return client
}
func (c stuffClient) DoStuff() error {
    return nil
}

以上实现了一个简单函数式可选模式,对 StuffClient 设定了默认值,并提供了 StuffClientOption 可以用来进行自定义更改。


上一篇: 多渠道账号的表设计想法
下一篇: 构建 CICD 遇到的 Docker in Docker 问题