go中函数选项模式

栏目: Go · 发布时间: 5年前

内容简介:作为golang开发人员,您将遇到的许多问题之一是尝试将函数的参数设置为可选。有时候使用默认设置,但有时候需要提供自定义设置。在许多语言中,这很容易;在c系列语言中,您可以使用不同数量的参数提供相同函数的多个版本,在php这样的语言中,您可以为参数提供默认值,并在调用方法时忽略它们。但在golang您不能做到这两点。那么在go中该如何实现呢?我们来看一个例子吧,假设我们有一个名为StuffClient的服务,它可以执行一些操作并具有两个配置选项(超时和重试):

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

移动应用的设计与开发

移动应用的设计与开发

[美] 弗林 (Brian Fling) / 马晶慧 / 电子工业出版社 / 2010-5 / 59.80元

本书全面介绍了如何在移动设备上设计和开发应用程序。书中从介绍移动产业的生态环境和移动媒体开始,阐述产品策划的方法、产品架构、视觉设计和产品类型的选择,并详细描述了产品实现过程中所用到的一些技术、工具和概念,最后还简单介绍了如何获得利润和降低成本,肯定了iPhone在移动设备发展史上起到的巨大推动作用。本书不仅能让读者了解到移动设计和开发的知识,更重要的是,它揭示了移动开发的代价高昂、标准混乱的根本......一起来看看 《移动应用的设计与开发》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器