Answers:
Go没有可选参数,也不支持方法重载:
如果方法分派也不需要进行类型匹配,则可以简化方法分派。其他语言的经验告诉我们,使用具有相同名称但签名不同的多种方法有时会很有用,但在实践中也可能会造成混淆和脆弱。在Go的类型系统中,仅按名称进行匹配并要求类型一致是一个简化的主要决定。
make
特例吗?还是不是真正实现为一个功能……
make
是一种语言构造,上面提到的规则不适用。请参阅此相关问题。
range
make
从这个意义上讲,情况是一样的
一种实现类似可选参数的好方法是使用可变参数。该函数实际上会接收您指定的任何类型的切片。
func foo(params ...int) {
fmt.Println(len(params))
}
func main() {
foo()
foo(1)
foo(1,2,3)
}
params
是一个整数切片
您可以使用包含以下参数的结构:
type Params struct {
a, b, c int
}
func doIt(p Params) int {
return p.a + p.b + p.c
}
// you can call it without specifying all parameters
doIt(Params{a: 1, c: 9})
对于任意的,可能大量的可选参数,一个好习惯是使用Functional options。
对于您的type Foobar
,首先只编写一个构造函数:
func NewFoobar(options ...func(*Foobar) error) (*Foobar, error){
fb := &Foobar{}
// ... (write initializations with default values)...
for _, op := range options{
err := op(fb)
if err != nil {
return nil, err
}
}
return fb, nil
}
其中每个选项都是使Foobar发生变化的函数。然后为您的用户提供方便的方式来使用或创建标准选项,例如:
func OptionReadonlyFlag(fb *Foobar) error {
fb.mutable = false
return nil
}
func OptionTemperature(t Celsius) func(*Foobar) error {
return func(fb *Foobar) error {
fb.temperature = t
return nil
}
}
为简洁起见,您可以为选项的类型命名(Playground):
type OptionFoobar func(*Foobar) error
如果需要强制性参数,请将它们作为构造函数的第一个参数添加到variadic之前options
。
功能选项成语的主要优点是:
该技术由Rob Pike创造,也由Dave Cheney演示。
func()
如果需要的话其属性可以是s),而不是让我的脑子绕这种方法。每当我必须使用这种方法(例如使用Echo库)时,我都会发现自己的大脑陷入了抽象的兔子洞。#fwiw
您可以将其很好地封装在类似于下面的函数中。
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
fmt.Println(prompt())
}
func prompt(params ...string) string {
prompt := ": "
if len(params) > 0 {
prompt = params[0]
}
reader := bufio.NewReader(os.Stdin)
fmt.Print(prompt)
text, _ := reader.ReadString('\n')
return text
}
在此示例中,提示默认情况下在前面有一个冒号和一个空格。。。
:
。。。但是,您可以通过为提示函数提供参数来覆盖它。
prompt("Input here -> ")
这将导致如下提示。
Input here ->
我最终使用了params和可变参数args的组合结构。这样,我不必更改多个服务使用的现有接口,并且我的服务能够根据需要传递其他参数。Golang游乐场中的示例代码:https://play.golang.org/p/G668FA97Nu
我来晚了一点,但是如果您喜欢流畅的界面,可以像下面这样设计链式呼叫的设置器:
type myType struct {
s string
a, b int
}
func New(s string, err *error) *myType {
if s == "" {
*err = errors.New(
"Mandatory argument `s` must not be empty!")
}
return &myType{s: s}
}
func (this *myType) setA (a int, err *error) *myType {
if *err == nil {
if a == 42 {
*err = errors.New("42 is not the answer!")
} else {
this.a = a
}
}
return this
}
func (this *myType) setB (b int, _ *error) *myType {
this.b = b
return this
}
然后这样称呼它:
func main() {
var err error = nil
instance :=
New("hello", &err).
setA(1, &err).
setB(2, &err)
if err != nil {
fmt.Println("Failed: ", err)
} else {
fmt.Println(instance)
}
}
这类似于@Ripounet答案上的“ 功能选项”惯用语,具有相同的优点,但有一些缺点:
err
变量并将其清零。但是,这可能会有一个小的优势,这种类型的函数调用应该更易于编译器内联,但我并不是专家。
您可以将任意命名参数与映射一起传递。
type varArgs map[string]interface{}
func myFunc(args varArgs) {
arg1 := "default" // optional default value
if val, ok := args["arg1"]; ok {
// value override or other action
arg1 = val.(string) // runtime panic if wrong type
}
arg2 := 123 // optional default value
if val, ok := args["arg2"]; ok {
// value override or other action
arg2 = val.(int) // runtime panic if wrong type
}
fmt.Println(arg1, arg2)
}
func Test_test() {
myFunc(varArgs{"arg1": "value", "arg2": 1234})
}
另一种可能性是使用带有字段的struct来指示其是否有效。sql中的空类型,例如NullString很方便。不必定义自己的类型很高兴,但是如果您需要自定义数据类型,则可以始终遵循相同的模式。我认为从函数定义中可以清楚地看出可选性,并且几乎没有额外的代码或工作。
举个例子:
func Foo(bar string, baz sql.NullString){
if !baz.Valid {
baz.String = "defaultValue"
}
// the rest of the implementation
}