英文:
Golang alternative to c++ function with default params: multiple functions, or struct param
问题
我想知道在Go语言中,与C++函数绑定默认参数的最佳实践是什么,这可能是对用户来说最容易看到函数参数的方式(通过linter的帮助)。你认为使用test函数的最GO风格和最简单的方法是什么?
以下是一个C++的示例函数:
void test(int x, int y=0, color=Color());
在Go语言中的等效方式有:
1. 使用多个函数签名:
func test(x int)
func testWithY(x int, y int)
func testWithColor(x int, color Color)
func testWithYColor(x int, y int, color Color)
优点:
- linter将显示test函数的所有可能性
- 编译器将采用最短的路径
缺点:
- 当参数很多时可能会变得复杂
2. 使用结构体参数:
type testOptions struct {
X int
Y int
color Color
}
func test(opt *testOptions)
// 用户调用
test(&testOptions{X: 5})
优点:
- 只有一个函数签名
- 可以只指定部分值
缺点:
- 需要定义一个结构体
- 值将由系统默认设置
借助模块github.com/creasty/defaults的帮助,可以设置默认值(但需要在运行时调用反射)。
type testOptions struct {
X int
Y int `default:"10"`
color Color `default:"{}"`
}
func test(opt *testOptions) *hg.Node {
if err := defaults.Set(opt); err != nil {
panic(err)
}
}
优点:
- 设置默认值
缺点:
- 在运行时使用反射
附注:
我看到了使用可变参数...
或/和interface{}
的方法,但我觉得很难知道要使用哪些参数(或者也许有一种方法可以向linter指示参数列表)。
英文:
I would like to know the best practice in Go equivalent to C++ functions binding with default params, which may be easiest for the user to see the function params(with linter help).
What do you think will be the most GO style and easiest way to use the test function ?
An example function in C++:
void test(int x, int y=0, color=Color());
Equivalence in Go
1. With multiple signatures:
func test(x int)
func testWithY(x int, y int)
func testWithColor(x int, color Color)
func testWithYColor(x int, y int, color Color)
pro:
- The linter will show all the possibilities for test
- The compiler will take the shortest path
cons:
- Can be overwhelmed when there is a lots of params
2. With struct parameter:
type testOptions struct {
X int
Y int
color Color
}
func test(opt *testOptions)
// user
test(&testOptions{x: 5})
pro:
- Only one signature
- Can specify only some values
cons:
- Need to define a struct
- The values will be set by default by the system
With the help of the module github.com/creasty/defaults, there is a way to set default values (but with the cost of calling reflect in runtime).
type testOptions struct {
X int
Y int `default:"10"`
color Color `default:"{}"`
}
func test(opt *testOptions) *hg.Node {
if err := defaults.Set(opt); err != nil {
panic(err)
}
}
pro:
- set default values
cons:
- Use of reflect in runtime
P.S.:
I saw the use of variadic parameters ...
or/with interface{}
but I find it not easy to know which params to use (or maybe there is a way to indicate a params list to the linter).
答案1
得分: 5
无论哪种方式都可以正常工作,但在Go语言中,函数选项模式可能更符合实现此功能的习惯用法。
它基于接受可变数量的WithXXX类型的函数参数的想法,这些参数可以扩展或修改调用的行为。
type Test struct {
X int
Y int
color Color
}
type TestOption func(*Test)
func test(x int, opts ...TestOption) {
p := &Test{
X: x,
Y: 12,
color: defaultColor,
}
for _, opt := range opts {
opt(p)
}
p.runTest()
}
func main() {
test(12)
test(12, WithY(34))
test(12, WithY(34), WithColor(Color{1, 2, 3}))
}
func WithY(y int) TestOption {
return func(p *Test) {
p.Y = y
}
}
func WithColor(c Color) TestOption {
return func(p *Test) {
p.color = c
}
}
英文:
Either way will work fine, but in Go the functional options pattern might be more idiomatic for implementing such functionality.
It is based on the idea of accepting a variable amount of WithXXX type of functional arguments that extend of modify the behavior of the call.
type Test struct {
X int
Y int
color Color
}
type TestOption func(*Test)
func test(x int, opts ...TestOption) {
p := &Test{
X: x,
Y: 12,
Color: defaultColor,
}
for _, opt := range opts {
opt(p)
}
p.runTest()
}
func main() {
test(12)
test(12, WithY(34))
test(12, WithY(34), WithColor(Color{1, 2, 3}))
}
func WithY(y int) TestOption {
return func(p *Test) {
p.Y = y
}
}
func WithColor(c Color) TestOption {
return func(p *Test) {
p.color = c
}
}
答案2
得分: 3
我认为,如果结果只有几个函数,那么你的选项1是一个很好的惯用选择。我还认为函数选项模式是一个不错的选择,它经常在工厂函数中使用。
另外,这个问题可能表明代码需要重构。test()
应该是一个类型的方法,该类型知道可选参数吗?
其中一个Go谚语是“让零值有用”,因此假设具有零值的类型意味着使用默认值。如果0
是您的int
类型的有效值,那么请考虑使用*int
,重点是避免引用您想保留默认值的字段。
package main
import (
"fmt"
)
const (
defaultY = 10
defaultColor = "blue"
)
type Color string
type Thing struct {
Y int
Color Color
}
func (t Thing) getY() int {
if t.Y == 0 {
return defaultY
}
return t.Y
}
func (t Thing) getColor() Color {
if t.Color == "" {
return defaultColor
}
return t.Color
}
func (t Thing) Test(x int) {
fmt.Println(x, t.getY(), t.getColor())
}
func main() {
Thing{}.Test(12)
Thing{Y: 11}.Test(12)
Thing{Y: 11, Color: "red"}.Test(12)
}
// 12 10 blue
// 12 11 blue
// 12 11 red
英文:
I think that your option 1 is a good idiomatic choice if the result is only a handful of functions. I also think the functional options pattern is a fine choice, it is often used in factory functions.
To add another choice, this question might be an indication that the code needs to be re-factored. Should test()
be a method on a type which knows the optional parameters?
One of the go proverbs is "make the zero value useful", so a type with its zero value is assumed to mean use the default. If 0
is a valid value for your int
type then consider *int
, the point is to avoid referencing fields where you want to keep the default.
package main
import (
"fmt"
)
const (
defaultY = 10
defaultColor = "blue"
)
type Color string
type Thing struct {
Y int
Color Color
}
func (t Thing) getY() int {
if t.Y == 0 {
return defaultY
}
return t.Y
}
func (t Thing) getColor() Color {
if t.Color == "" {
return defaultColor
}
return t.Color
}
func (t Thing) Test(x int) {
fmt.Println(x, t.getY(), t.getColor())
}
func main() {
Thing{}.Test(12)
Thing{Y: 11}.Test(12)
Thing{Y: 11, Color: "red"}.Test(12)
}
// 12 10 blue
// 12 11 blue
// 12 11 red
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论