如何处理`New(…)`函数中的长参数列表

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

How to deal with long parameter lists in `New(...` functions

问题

假设我有一个名为MyStruct的本地化结构体,其具有以下结构:

struct MyStruct {
  myField1 string
  myField2 string
  myField3 string
  ...
  myFieldN string
}

还有一个用于为外部调用者实例化新的MyStruct的函数:

func NewMyStruct(myField1, myField2, myField3, ..., myFieldN string) MyStruct {
  return MyStruct{
    myField1: myField1,
    myField2: myField2,
    myField3: myField3,
    ...
    myFieldN: myFieldN,
  }
}

问题:如果结构体中的字段太多,导致NewMyStruct(...函数的参数过多,我该如何处理这种情况? 有没有什么最佳实践来减轻这个问题?目前,我的代码库中有几个类似这样的函数:

func NewSuperStruct(myField1, myField2, myField3, myField4, myField5, myField6, myField7, myField8, myField9, myField10, myField11, myField12, myField13, myField14, myField15, myField16, myField17, myField18, myField19, myField20, myField21, myField22) ...

但并不是说这些结构体本身是无意义的,也就是说这些属性/字段不属于它们,在我的应用程序中它们是有意义的,只是结构体太大了,仅此而已。

英文:

Say I have a localised struct called MyStruct with the following body:

struct MyStruct {
  myField1 string
  myField2 string
  myField3 string
  ...
  myFieldN string
}

And a function which instantiates new MyStructs for external callers:

func NewMyStruct(myField1, myField2, myField3, ..., myFieldN string) MyStruct {
  return MyStruct{
    myField1: myField1,
    myField2: myField2,
    myField3: myField3,
    ...
    myFieldN: myFieldN,
  }
}

Question: How would I best deal with the scenario of there being just too many fields within the struct resulting in a NewMyStruct(... function with way too many parameters? Is there any best practice to mitigate this issue? As of now, I have several functions like this in my codebase:

func NewSuperStruct(myField1, myField2, myField3, myField4, myField5, myField6, myField7, myField8, myField9, myField10, myField11, myField12, myField13, myField14, myField15, myField16, myField17, myField18, myField19, myField20, myField21, myField22) ...

But it's not necessarily that the structs themselves are nonsensical in the sense that the properties/fields don't belong within, in my application they do make sense, the structs are just too large, that's all.

答案1

得分: 4

我会说,不要使用New函数:

type MyStruct struct {
  myField1 string
  myField2 string
  myField3 string
}

val := MyStruct{
    myField1: "one",
    myField2: "two",
    myField3: "three",
}

如果需要从另一个包设置未导出字段,请使用某种选项或配置:

type MyStruct struct {
    Exported   string
    unexported string
}

type MyStructOptions struct {
    Exported   string
    Unexported string
}

func NewMyStruct(opts MyStructOptions) *MyStruct {
    return &MyStruct{
        Exported: opts.Exported,
        unexported: opts.Unexported,
    }
}
英文:

I'd say just don't have the New func:

struct MyStruct {
  myField1 string
  myField2 string
  myField3 string
}

val := MyStruct{
    myField1: "one",
    myField2: "two",
    myField3: "three",
}

If unexported fields need to be set from another package, use some kind of options or config:

type MyStruct struct {
    Exported   string
    unexported string
}

type MyStructOptions struct {
    Exported   string
    Unexported string
}

func NewMyStruct(opts MyStructOptions) *MyStruct {
    return &MyStruct{
        Exported: opts.Exported,
        unexported: opts.Unexported,
    }
}

答案2

得分: 3

个人而言(显然取决于结构的目标),我非常喜欢函数选项:

type MyStructOpts func(*MyStruct)

func WithField1(field1 string) MyStructOps {
  return func(m *MyStruct) {
    m.myField1 = field1
  }
}

func New(opts ...MyStructOpts) *MyStruct {
  m := MyStruct{
    myField1: "someDefaultIfneeded",
  }

  for _, opt := range opts {
    opt(&m)
  }

  return &m
}

可以这样使用:

New(
  WithField1("someString"),
  ...
)

这有几个好处:

  • 调用者不需要担心顺序问题
  • 通过字段名显式传递值,这意味着你不会混淆Field1和Field2
  • 你可以在调用者不传递WithField1的情况下,为MyStruct传递不同的默认值
  • 添加更多字段不会导致必须更新New的所有调用者
英文:

Personally (obviously depending on the goal of the struct) I am a big fan of functional options:

type MyStructOpts func(*MyStruct)

func WithField1(field1 string) MyStructOps {
  return func(m *MyStruct) {
    m.myField1 = field1
  }
}

func New(opts ...MyStructOpts) *MyStruct {
  m := MyStruct{
    myField1: "someDefaultIfneeded",
  }

  for _, opt := range opts {
    opt(&m)
  }

  return &m
}

which can be used as follows:

New(
  WithField1("someString"),
  ...
)

This has a couple of benefits:

  • Callers of new do not need to worry about the order
  • Passing values is explicit with field name, which means you won't mix up Field1 & Field2
  • You have the ability to pass different defaults to MyStruct in case callers don't pass WithField1
  • Adding more fields doesn't lead to having to update all callers of New

huangapple
  • 本文由 发表于 2022年3月17日 22:18:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/71513894.html
匿名

发表评论

匿名网友

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

确定