Skip to content

Latest commit

 

History

History
296 lines (259 loc) · 9.24 KB

9.1.md

File metadata and controls

296 lines (259 loc) · 9.24 KB

9.1 Go 函数可变数量参数传参

众所周知,Go语言是严格类型语言,而开发的时候又遇到传入参数不定的情况,怎么办?golang 为我们提供了接入多值参数用于解决这个问题。

1、示例:

package main

import "fmt"

func main() {
	//multiParam 可以接受可变数量的参数
	multiParam("jerry", "herry")
	multiParam("php", "mysql", "js")
}
func multiParam(args ...string) {
	//接受的参数放在args数组中
	for _, e := range args {
		fmt.Println(e)
	}
}

需要注意的是,可变参数是函数最右边的参数。如

2、示例:

package main

import "fmt"

func main() {
   //multiParam 可以接受可变数量的参数
   multiParam("jerry", 1)
   multiParam("php", 1, 2)
}
func multiParam(name string, args ...int) {
   fmt.Println(name)
   //接受的参数放在args数组中
   for _, e := range args {
      fmt.Println(e)
   }
}

可以向一个已有的切片加可变参数运算符 ”…“ 后缀的方式将其传入可变参数函数。

3、示例:

package main

import "fmt"

func multiParam(args ...string) {
   for _, e := range args {
      fmt.Println(e)
   }
}
func main() {
   names := []string{"jerry", "herry"}
   multiParam(names...)   
   
}

它和multiParam("jerry", "herry") 写法是效果一样,只是可变参数传值方法不会创建新的切片,直接使用传递的切片。

选项模式

作为 Golang 开发人员,您将遇到的众多问题之一是试图将函数的参数设为可选。这是一个非常常见的用例,您有一些对象应该使用一些基本的默认设置开箱即用,并且您可能偶尔想要提供一些更详细的配置。说白了就是函数传参的时候需要部分参数有默认值,当这些参数不传时,使用默认值。 让我们看一个例子。假设我们有一些名为 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,否则我们会得到一个编译错误,比如“NewStuffClient redeclared in this block”。

一种选择是创建另一个具有不同名称的构造函数,例如:

func NewStuffClient(conn Connection) StuffClient {
    return &stuffClient{
        conn:    conn,
        timeout: DEFAULT_TIMEOUT,
        retries: DEFAULT_RETRIES,
    }
}
func NewStuffClientWithOptions(conn Connection, timeout, retries int) StuffClient {
    return &stuffClient{
        conn:    conn,
        timeout: timeout,
        retries: retries,
    }
}

但这有点蹩脚。我们可以做得更好。如果我们传入一个配置对象会怎样:

type StuffClientOptions struct {
    Retries int //number of times to retry the request before giving up
    Timeout int //connection timeout in seconds
}
func NewStuffClient(conn Connection, options StuffClientOptions) StuffClient {
    return &stuffClient{
        conn:    conn,
        timeout: options.Timeout,
        retries: options.Retries,
    }
}

但这也不是很好。现在,即使我们不想指定任何选项,我们也必须始终创建此结构并将其传入。而且我们也没有自动填充默认值,除非我们在代码中的某处添加一堆检查,或者我们公开了一个可以传入的 DefaultStuffClientOptions 变量(也不好,因为它可以在一个地方修改,这可能会导致问题别的地方)。

那么有什么解决办法呢?解决这个困境的最好方法是使用功能选项模式,利用 Go 对闭包的方便支持。让我们保留上面定义的这个 StuffClientOptions,但我们会添加一些东西:

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
    }
}

一清二楚,对吗?这里到底发生了什么?基本上,我们的结构定义了 StuffClient 的可用选项。此外,现在我们已经定义了一个叫做 StuffClientOption(这次是单数)的东西,它只是一个接受我们的 options 结构作为参数的函数。我们还定义了几个函数,分别称为 WithRetries 和 WithTimeout,它们返回一个闭包。现在魔法来了:

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,
    }
}

我们现在已经定义了一个额外的未公开变量,其中包含我们的默认选项,并且我们现在已经调整了我们的构造函数以接受可变参数参数。然后我们遍历 StuffClientOption(单数)的列表,对于它们中的每一个,我们将返回的闭包应用到我们的 options 变量(回想一下,这些闭包接受一个 StuffClientOptions 变量并简单地修改它的选项值)。

现在我们要做的就是使用它:

x := NewStuffClient(Connection{})
fmt.Println(x) // prints &{{} 2 3}
x = NewStuffClient(
    Connection{},
    WithRetries(1),
)
fmt.Println(x) // prints &{{} 2 1}
x = NewStuffClient(
    Connection{},
    WithRetries(1),
    WithTimeout(1),
)
fmt.Println(x) // prints &{{} 1 1}

这看起来很不错,现在可以用了。关于它的好处是,我们可以非常轻松地随时添加新选项,只需对代码进行极少的更改。

把它们放在一起,我们有这样的东西:

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
}

如果您想自己尝试一下,请在Go Playground 上查看。

但这可以通过删除 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
}

使用示例

conn:=Connection{}
ret:=NewStuffClient(conn)
fmt.Println(ret)//{{} 2 3}
ret=NewStuffClient(conn,WithTimeout(5))
fmt.Println(ret)//{{} 2 5}

这里 尝试一下。在我们的示例中,我们只是将配置直接应用于我们的结构,在中间有一个额外的配置结构是没有意义的。但是,请注意,在许多情况下,您可能仍希望使用上一个示例中的 config 结构;例如,如果您的构造函数使用配置选项来执行某些操作,但没有将它们存储到结构中,或者如果它们被传递到其他地方。config struct 变体是更通用的实现。

感谢罗布·派克和戴夫·切尼为推广这种设计模式。

links

https://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/