在Go语言的结构体构造函数中使用默认值的可选参数。

huangapple go评论80阅读模式
英文:

Optional parameters with defaults in Go struct constructors

问题

我发现自己在Go结构体构造函数中使用以下模式来获取具有默认值的可选参数:

package main

import (
	"fmt"
)

type Object struct {
	Type int
	Name string
}

func NewObject(obj *Object) *Object {
	if obj == nil {
		obj = &Object{}
	}
	// Type的默认值为1
	if obj.Type == 0 {
		obj.Type = 1
	}
	return obj
}

func main() {
	// 创建Name为"foo",Type为1的对象
	obj1 := NewObject(&Object{Name: "foo"})
	fmt.Println(obj1)

	// 创建Name为空字符串,Type为1的对象
	obj2 := NewObject(nil)
	fmt.Println(obj2)

	// 创建Name为"bar",Type为2的对象
	obj3 := NewObject(&Object{Type: 2, Name: "foo"})
	fmt.Println(obj3)
}

是否有更好的方法来实现具有默认值的可选参数?

英文:

I've found myself using the following pattern as a way to get optional parameters with defaults in Go struct constructors:

package main

import (
	"fmt"
)

type Object struct {
	Type int
	Name string
}

func NewObject(obj *Object) *Object {
	if obj == nil {
		obj = &Object{}
	}
	// Type has a default of 1
	if obj.Type == 0 {
		obj.Type = 1
	}
	return obj
}

func main() {
	// create object with Name="foo" and Type=1
	obj1 := NewObject(&Object{Name: "foo"})
	fmt.Println(obj1)

	// create object with Name="" and Type=1
	obj2 := NewObject(nil)
	fmt.Println(obj2)

	// create object with Name="bar" and Type=2
	obj3 := NewObject(&Object{Type: 2, Name: "foo"})
	fmt.Println(obj3)
}

Is there a better way of allowing for optional parameters with defaults?

答案1

得分: 5

Dave Cheney提供了一个很好的解决方案,其中你可以使用功能选项来覆盖默认值:

https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

所以你的代码将变成:

package main

import (
    "fmt"
)

type Object struct {
    Type int
    Name string
}

func NewObject(options ...func(*Object)) *Object {
    // 使用默认值设置对象
    obj := &Object{Type: 1}
    // 如果有选项,则应用选项
    for _, option := range options {
        option(obj)
    }
    return obj
}

func WithName(name string) func(*Object) {
    return func(obj *Object) {
        obj.Name = name
    }
}

func WithType(newType int) func(*Object) {
    return func(obj *Object) {
        obj.Type = newType
    }
}

func main() {
    // 创建Name="foo"和Type=1的对象
    obj1 := NewObject(WithName("foo"))
    fmt.Println(obj1)

    // 创建Name=""和Type=1的对象
    obj2 := NewObject()
    fmt.Println(obj2)

    // 创建Name="bar"和Type=2的对象
    obj3 := NewObject(WithType(2), WithName("foo"))
    fmt.Println(obj3)
}

https://play.golang.org/p/pGi90d1eI52

英文:

Dave Cheney offered a nice solution to this where you have functional options to overwrite defaults:

https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

So your code would become:

package main

import (
    "fmt"
)

type Object struct {
    Type int
    Name string
}

func NewObject(options ...func(*Object)) *Object {
    // Setup object with defaults 
    obj := &Object{Type: 1}
    // Apply options if there are any
    for _, option := range options {
        option(obj)
    }
    return obj
}

func WithName(name string) func(*Object) {
    return func(obj *Object) {
        obj.Name = name
    }
}

func WithType(newType int) func(*Object) {
    return func(obj *Object) {
        obj.Type = newType
    }
}

func main() {
    // create object with Name="foo" and Type=1
    obj1 := NewObject(WithName("foo"))
    fmt.Println(obj1)

    // create object with Name="" and Type=1
    obj2 := NewObject()
    fmt.Println(obj2)

    // create object with Name="bar" and Type=2
    obj3 := NewObject(WithType(2), WithName("foo"))
    fmt.Println(obj3)
}

https://play.golang.org/p/pGi90d1eI52

答案2

得分: 3

这个方法对我来说似乎是合理的。然而,你有一个错误。如果我明确将Type设置为0,它会被切换为1。

我建议的修复方法是:使用结构体字面值作为默认值。

或者也可以将其提取出来。

英文:

The approach seems reasonable to me. However, you have a bug. If I explicitly set Type to 0, it will get switched to 1.

My suggested fix: Use a struct literal for the default value: http://play.golang.org/p/KDNUauy6Ie

Or perhaps extract it out: http://play.golang.org/p/QpY2Ymze3b

答案3

得分: 1

请看一下《Effective Go》中的“使用new进行分配”。它们解释了如何将零值结构体作为有用的默认值。

如果你可以让Object.Type(以及其他字段)的默认值为零,那么Go的结构体字面量已经提供了你所需的功能。

根据复合字面量一节的说明:

复合字面量的字段按顺序排列,必须全部存在。然而,通过将元素明确标记为字段:值对,初始化器可以以任意顺序出现,缺失的字段将使用它们各自的零值。

这意味着你可以将这段代码:

obj1 := NewObject(&Object{Name: "foo"})
obj2 := NewObject(nil)
obj3 := NewObject(&Object{Type: 2, Name: "foo"})

替换为:

obj1 := &Object{Name: "foo"}
obj2 := &Object{}
obj3 := &Object{Type: 2, Name: "foo"}

如果无法将零值作为所有字段的默认值,推荐的方法是使用构造函数。例如:

func NewObject(typ int, name string) *Object {
    return &Object{Type: typ, Name: name}
}

如果你希望Type具有非零的默认值,可以添加另一个构造函数。假设Foo对象是默认对象,其Type为1。

func NewFooObject(name string) *Object {
    return &Object{Type: 1, Name: name}
}

你只需要为每组非零默认值编写一个构造函数。通过将某些字段的语义更改为零默认值,你总是可以减少该组的大小。

此外,注意添加一个具有零默认值的新字段到Object中不需要对上述任何代码进行更改,因为所有结构体字面量都使用了标记初始化。这在后续的开发中非常方便。

英文:

Take a look at "Allocation with new" in Effective Go. They explain about making zero-value structs a useful default.

If you can make Object.Type (and your other fields) have a default of zero, then Go struct literals already give you exactly the feature you're requesting.

From the section on composite literals:
>The fields of a composite literal are laid out in order and must all be present. However, by labeling the elements explicitly as field:value pairs, the initializers can appear in any order, with the missing ones left as their respective zero values.

That means you can replace this:

obj1 := NewObject(&Object{Name: "foo"})
obj2 := NewObject(nil)
obj3 := NewObject(&Object{Type: 2, Name: "foo"})

with this:

obj1 := &Object{Name: "foo"}
obj2 := &Object{}
obj3 := &Object{Type: 2, Name: "foo"}

If it is not possible to make the zero value the default for all of your fields, the recommended approach is a constructor function. For example:

func NewObject(typ int, name string) *Object {
    return &Object{Type: typ, Name: name}
}

If you want Type to have a nonzero default, you can add another constructor function. Suppose Foo objects are the default and have Type 1.

func NewFooObject(name string) *Object {
    return &Object{Type: 1, Name: name}
}

You only need to make one constructor function for each set of nonzero defaults you use. You can always reduce that set by changing the semantics of some fields to have zero defaults.

Also, note that adding a new field to Object with a zero default value doesn't require any code changes above, because all struct literals use labeled initialization. That comes in handy down the line.

答案4

得分: 1

这是一个使用对象方法设置默认值的替代方案。我发现它在某些情况下很有用,尽管与你的代码没有太大区别。如果它是一个包的一部分,这种方法可能会更好地使用。我不是Go专家,也许你有一些额外的意见。

package main

import (
	"fmt"
)

type defaultObj struct {
	Name      string
	Zipcode   int
	Longitude float64
}

func (obj *defaultObj) populateObjDefaults() {
	if obj.Name == "" {
		obj.Name = "Named Default"
	}
	if obj.Zipcode == 0 {
		obj.Zipcode = 12345
	}
	if obj.Longitude == 0 {
		obj.Longitude = 987654321
	}
}

func main() {
	testdef := defaultObj{Name: "Mr. Fred"}
	testdef.populateObjDefaults()
	fmt.Println(testdef)

	testdef2 := defaultObj{Zipcode: 90210}
	testdef2.populateObjDefaults()
	fmt.Println(testdef2)

	testdef2.Name = "Mrs. Fred"
	fmt.Println(testdef2)

	testdef3 := defaultObj{}
	fmt.Println(testdef3)
	testdef3.populateObjDefaults()
	fmt.Println(testdef3)
}

输出:

{Mr. Fred 12345 9.87654321e+08}
{Named Default 90210 9.87654321e+08}
{Mrs. Fred 90210 9.87654321e+08}
{ 0 0}
{Named Default 12345 9.87654321e+08}
英文:

https://play.golang.org/p/SABkY9dbCOD

Here's an alternative that uses a method of the object to set defaults. I've found it useful a few times, although it's not much different than what you have. This might allow better usage if it's part of a package. I don't claim to be a Go expert, maybe you'll have some extra input.

package main

import (
    "fmt"
)

type defaultObj struct {
    Name      string
    Zipcode   int
    Longitude float64
}

func (obj *defaultObj) populateObjDefaults() {
    if obj.Name == "" {
	    obj.Name = "Named Default"
    }
    if obj.Zipcode == 0 {
	    obj.Zipcode = 12345
    }
    if obj.Longitude == 0 {
	    obj.Longitude = 987654321
    }
}

func main() {
    testdef := defaultObj{Name: "Mr. Fred"}
    testdef.populateObjDefaults()
    fmt.Println(testdef)

    testdef2 := defaultObj{Zipcode: 90210}
    testdef2.populateObjDefaults()
    fmt.Println(testdef2)

    testdef2.Name = "Mrs. Fred"
    fmt.Println(testdef2)

    testdef3 := defaultObj{}
    fmt.Println(testdef3)
    testdef3.populateObjDefaults()
    fmt.Println(testdef3)
}

Output:

{Mr. Fred 12345 9.87654321e+08}
{Named Default 90210 9.87654321e+08}
{Mrs. Fred 90210 9.87654321e+08}
{ 0 0}
{Named Default 12345 9.87654321e+08}

答案5

得分: 0

你可以使用...运算符。

与Python中的ToCall(a=b)不同,你可以写成ToCall("a", b)。

请参考Go Play示例

func GetKwds(kwds []interface{}) map[string]interface{} {
    result := make(map[string]interface{})

    for i := 0; i < len(kwds); i += 2 {
        result[kwds[i].(string)] = kwds[i+1]
    }

    return result
}

func ToCall(kwds ...interface{}) {
    args := GetKwds(kwds)
    if value, ok := args["key"]; ok {
        fmt.Printf("key: %#v\n", value)
    }
    if value, ok := args["other"]; ok {
        fmt.Printf("other: %#v\n", value)
    }
}

func main() {
    ToCall()
    ToCall("other", &map[string]string{})
    ToCall("key", "Test", "other", &Object{})
}
英文:

You could use the ... operator.

instead of writing ToCall(a=b) like in python you write, ToCall("a",b)

See the Go Play Example

func GetKwds(kwds []interface{}) map[string]interface{} {
	result := make(map[string]interface{})

	for i := 0; i &lt; len(kwds); i += 2 {
		result[kwds[i].(string)] = kwds[i+1]
	}

	return result
}

func ToCall(kwds ...interface{}) {
	args := GetKwds(kwds)
	if value, ok := args[&quot;key&quot;]; ok {
		fmt.Printf(&quot;key: %#v\n&quot;, value)
	}
	if value, ok := args[&quot;other&quot;]; ok {
		fmt.Printf(&quot;other: %#v\n&quot;, value)
	}
}

func main() {
	ToCall()
	ToCall(&quot;other&quot;, &amp;map[string]string{})
	ToCall(&quot;key&quot;, &quot;Test&quot;, &quot;other&quot;, &amp;Object{})

}

huangapple
  • 本文由 发表于 2013年11月23日 01:13:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/20150628.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定