go中函数选项模式-创新互联

作为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

另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。


文章标题:go中函数选项模式-创新互联
本文URL:http://ybzwz.com/article/dhecdj.html