go使用options模式设置参数
作者:程序员CKeen
博客:http://ckeen.cn
长期坚持做有价值的事!积累沉淀,持续成长,升维思考!希望把编码作为长期兴趣爱好😄
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的模式
bilibili开源框架kratos: https://github.com/go-kratos/kratos/blob/main/options.go
beego框架封装的httpclient: beego/httpclient.go at develop · beego/beego · GitHub
etc...