作为golang开发人员,您将遇到的许多问题之一是尝试将函数的参数设置为可选。有时候使用默认设置,但有时候需要提供自定义设置。
在许多语言中,这很容易;在c系列语言中,您可以使用不同数量的参数提供相同函数的多个版本,在php这样的语言中,您可以为参数提供默认值,并在调用方法时忽略它们。但在golang您不能做到这两点。那么在go中该如何实现呢?
我们来看一个例子吧,假设我们有一个名为StuffClient的服务,它可以执行一些操作并具有两个配置选项(超时和重试):
type StuffClient interface {
DoStuff() error
}
type stuffClient struct {
conn Connection
timeout int
retries int
}
该结构是私有的,所以我们应该为它提供某种构造函数
func NewStuffClient(conn Connection, timeout, retries int) StuffClient {
return &stuffClient {
conn: conn,
timeout: timeout,
retries: retries,
}
}
但现在我们总是要在每次调用NewStuffClient时提供超时和重试。大多数时候我们只想使用默认值。我们无法使用不同数量的参数定义多个版本的NewStuffClient, 否则我们将得到一个编译错误。
一种选择时创建另一个具有不同名称的构造函数,例如
func NewStuffClient (conn Connection) StuffClient {
return &stuffClient {
conn: conn,
timeout: DefaultTimeout,
retries: DefaultRetries,
}
}
func NewStuffClienWithOptions(conn Connection, timeout, retries int) StuffClient {
return &stuffClient {
conn: conn,
timeout: timeout,
retries: retries,
}
}
我们还可以做的更好,将所有选项放到配置对象中
type StuffClientOptions struct {
Retries int
Timeout int
}
func NewStuffClient(conn Connection, options StuffClientOptions) StuffClient {
return &stuffClient {
conn: conn,
timeout: options.Timeout,
retries: options.Retries,
}
}
但那也不是很好,现在我们必须这个结构并传入它,即使我们不想指定任何选项,我们也没有自动填写的默认值,除非我们在代码中添加了一堆检查或者我们可以传入一个DefaultSuffClientOptions变量(但这可能会导致在一个地方被修改,影响别的地方)
那么解决方案是什么?解决这个难题的最好方法就是使用函数选项模式,利用go闭包的方便支持,让我们保留上面定义的 StuffClientOptions,但我们会添加一些东西:
type StuffClientOption func(*StuffClientOptions)
type StuffClientOptions struct {
Retries int
Timeout int
}
func WithRetries(r int) StuffClientOption {
return func(o *StuffClientOptions) {
o.retries = r
}
}
func WithTimeout(t int) StuffClientOption {
return func(o *StuffClientOptions) {
o.timeout = t
}
}
var defaultStuffClientOptions = StuffClientOptions {
Retries: 3,
Timeout: 2,
}
func NewStuffClient(conn Connection, opts ...StuffClientOption) StuffClient {
options := defaultStuffClientOptions
for _, o := range opts {
o(&options)
}
return &stuffClient{
conn: conn,
timeout: options.Timeout,
retries: options.Retries,
}
}
现在看起来已经非常好用了。关于它的好处是我们可以随时添加新选项,只需要对代码进行少量的更改。
var defaultStuffClientOptions = StuffClientOptions{
Retries: 3,
Timeout: 2,
}
type StuffClientOption func(*StuffClientOptions)
type StuffClientOptions struct {
Retries int //number of times to retry the request before giving up
Timeout int //connection timeout in seconds
}
func WithRetries(r int) StuffClientOption {
return func(o *StuffClientOptions) {
o.Retries = r
}
}
func WithTimeout(t int) StuffClientOption {
return func(o *StuffClientOptions) {
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 {
options := defaultStuffClientOptions
for _, o := range opts {
o(&options)
}
return &stuffClient{
conn: conn,
timeout: options.Timeout,
retries: options.Retries,
}
}
func (c stuffClient) DoStuff() error {
return nil
}
我们也可以通过删除 StuffClientOptions 结构并将选项直接应用于我们的StuffClient, 可以进一步简化这一过程
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
}
在我们的示例中,只是将配置直接应用于结构,在中间有一个额外的配置结构是没有意义的,但请注意,在许多情况下,您可能仍希望使用上一个示例中的config结构,例如:如果你的构造函数使用配置选项来执行某些操作但并没有将它们存储到结构中,或者他们被传递到其他地方。config结构变量是更通用的实现。
使用步骤
- 定义选项config结构体
type options struct{ timeout time.Duration }
- 定义默认config结构体变量
var defaultOptions = options{}
- 定义配置选项函数
type option func(*options) func WithTimeout(t time.Duration) options { return func(o *options) { o.timeout = t } }
- 应用函数选项配置
func Do(opts ...option) { d := defaultOptions for _, o := range opts { o(&d) } }
原文链接:
https://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/
相关参考连接
Dave Cheney: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
Rob Pike: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
有疑问加站长微信联系(非本文作者)