众所周知,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 变体是更通用的实现。
感谢罗布·派克和戴夫·切尼为推广这种设计模式。
https://halls-of-valhalla.org/beta/articles/functional-options-pattern-in-go,54/