构造函数的多个参数

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

Constructor with many arguments

问题

初始化具有许多必需参数的Go类型的最佳方式是什么?

例如:

type Appointment struct {
  Title       string
  Details     string
  Dresscode   string
  StartingTime int64
  EndingTime  int64
  RSVPdate    int64
  Place       *Place
  Guests      []*Guest
}

type Place struct {
  Name    string
  Address string
}

type Guest struct {
  Name   string
  Status string
}

我希望Appointment类型始终有效;也就是说,我不想使用结构体字面量进行初始化,然后再进行验证。

不想要:

a := &Appointment{
  Title:        "foo",
  Details:      "bar",
  StartingTime: 12451412,
  ...
}

err := a.Validate()

在不必在构造函数参数中提供所有参数的情况下,如何初始化这种类型的对象最好?

英文:

What's the most idiomatic way of initializing a Go type with many required parameters?

For example:

type Appointment struct {
  Title string
  Details string
  Dresscode string

  StartingTime int64
  EndingTime int64
  RSVPdate int64


  Place *Place
  Guests []*Guest
}

type Place struct {
  Name string
  Address string
}

type Guest struct {
  Name string
  Status string
}

I want the Appointment type to be always valid; that is, I don't want to initialize it with a struct literal and then have to validate it.

Don't want:

a := &Appointment{
  Title: "foo",
  Details: "bar",
  StartingTime: 12451412,
  ...
}

err := a.Validate()

whats the best way to initialize this type of object (with lots of fields) without having to supply all the arguments in the constructor arguments?

答案1

得分: 9

你可以使用“功能选项”模式来实现这个。它允许你为每个输入定义函数,从而无需将大量选项传递给构造函数。

func New(options ...func(*Appointment)) (*Appointment, error) {
  ap := &Appointment{
    Title: "Set your defaults",
    Details: "if you don't want zero values",
    StartingTime: 123,
  }
  for _, option := range options {
    option(ap)
  }
  // 在这里进行任何最终验证
  // 例如,检查某些值是否仍为零值
  if ap.EndTime == 0 {
    return nil, errors.New("invalid end time")
  }

  return ap, nil
}

// 然后定义你的选项函数

func AtEndTime(endTime int64) func(*Appointment) {
  return func(ap *Appointment) {
    ap.EndTime = endTime
  }       
}

调用的结果类似于:

ap, err := appointment.New(
  AtEndTime(123),
  WithGuests([]Guest{...}),
)

如果你想在函数本身中验证每个选项,将该签名更改为可能返回错误也不是太麻烦。

英文:

You may be able to use the "functional options" pattern to achieve this. It allows you to define functions for each input, removing the need for you to pass lots of options to your constructor.

func New(options ...func(*Appointment)) (*Appointment, error) {
  ap := &Appointment{
    Title: "Set your defaults",
    Details: "if you don't want zero values",
    StartingTime: 123,
  }
  for _, option := range options {
    option(ap)
  }
  // Do any final validation that you want here.
  // E.g. check that something is not still 0 value
  if ap.EndTime == 0 {
    return nil, errors.New("invalid end time")
  }

  return ap, nil
}

// Then define your option functions

func AtEndTime(endTime int64) func(*Appointment) {
  return func(ap *Appointment) {
    ap.EndTime = endTime
  }       
}

The resulting call looks something like:

ap, err := appointment.New(
  AtEndTime(123),
  WithGuests([]Guest{...}),
)

If you want to validate each option in the function itself, it's not too much work to change that signature to possibly return an error too.

答案2

得分: 5

你可以避免将10个以上的参数传递给构造函数的一种方法是为每个Xxx类型创建一个XxxParams类型,并让NewXxx函数以该参数类型作为参数。然后,NewXxx构造函数将使用这些参数构造一个Xxx值,对其进行验证,并根据验证结果返回该值或错误。

如果你手动构造XxxParams值而不是从JSON、XML等进行解组,可能会感到有些冗余;但是,通过这种方式,你可以强制只构造有效的Xxx,并将可能无效的状态保留在输入的XxxParams中。

这里有一个来自Stripe存储库的示例:AccountAccountParams,以及构造函数

英文:

One way you could avoid having to pass 10+ arguments to your constructors is to have an XxxParams type for each of your Xxx types and have your NewXxx take that params type as its argument. Then the NewXxx constructor would construct an Xxx value from those params, validate it, and return it, or an error, depending on the result of the validation.

This might feel redundant if you're constructing the XxxParams values manually as opposed to unmarshaling them from json, xml, etc.; but still, this way you are enforcing, however loosely, only valid Xxx's to be constructed, keeping the possibly invalid state in the input (XxxParams).

Here's an example from Stripe's repo: Account, AccountParams, and constructor

答案3

得分: 0

一种常见的Go项目中使用的模式是创建返回结构体所需状态的函数(可以查看httprouter项目作为示例,尽管它的New函数不接受任何参数...)

在你的情况下,你可以编写一个函数,返回已初始化了所需属性的Appointment

例如:

package appointment

type Appointment struct {
    // 在这里写入你的示例代码...
}

func New(title, details, dressCode string) *Appointment {
    return &Appointment{
        Title:        "foo",
        Details:      "bar",
        StartingTime: 12451412,
        // 使用合理的默认值设置其余属性,否则它们将初始化为零值
    }
}

然后在另一个文件中导入该包:

package main

import "path/to/appointment"

func main() {
    myApt := appointment.New("Interview", "Marketing Job", "Casual")
    // myApt 现在是一个正确初始化的 Appointment 结构体的指针
}

根据你对 Appointment 对象属性值的访问控制要求,你不必导出它们(将它们设置为小写),并在结构体本身上提供更传统的访问器(例如 get、set)方法,以确保结构体始终保持“有效”。

英文:

One pattern seen used by popular Go project is to create functions that return desired state of a struct. (checkout the httprouter project as an example - although its New func does not take any args...)

In your case - you could write a function that returns an Appointment with desired properties initialized.

for example

package appointment 

type Appointment struct { 
    //your example code here...
}

func New(title, details, dressCode string) *Appointment {
  return &Appointment{
    Title: "foo",
    Details: "bar",
    StartingTime: 12451412,
    //set the rest of the properties with sensible defaults - otherwise they will initialize to their zero value
  }  
}

Then used in another file, import the package

package main

import path/to/appointment

func main() {
   myApt := appointment.New("Interview", "Marketing Job", "Casual")
   //myApt is now a pointer to an Appointment struct properly initialized
}

Depending on how tight you want access control of the Appointment object property values, you do not have to export all of them (by setting them to lowercase) and provide more traditional accessor (think get, set) methods on the struct itself to ensure the struct always remain "valid"

答案4

得分: 0

我希望Appointment类型始终有效;也就是说,我不想用结构体字面量初始化它,然后再进行验证。

唯一能保证这一点的方法是不导出该类型。这样,你的包的消费者只能通过你的构造方法获得该类型的结构体。请记住,返回未导出的类型有点不太美观。解决这个问题的一种可能方法是通过导出的接口访问数据。这会带来许多其他考虑因素,这可能对于特定情况来说是好事,也可能是坏事。

尽管这是满足你所说需求的唯一方法,但实际上可能并不必要。你可以考虑放宽你的要求。

考虑以下内容:

你必须验证输入数据!

你要决定的只是你是在对象创建时还是在对象使用时进行验证。Go的结构通常使后者更容易(对于编码者和数据的使用者)。如果你确实必须在对象创建时进行验证,那么你唯一的选择就是使用未导出的类型,并为每个字段使用getter/setter方法。

英文:

> I want the Appointment type to be always valid; that is, I don't want to initialize it with a struct literal and then have to validate it.

The only way to guarantee this is to not export the type. Then the only way for a consumer of your package to obtain a struct of that type, is through your constructor method. Keep in mind that returning non-exported types is kind of ugly. One possible way around this is to access your data through an exported interface. That brings in a number of other considerations--which may be good, or bad for any given situation.

Now, while that's the only way to strictly meet your stated requirement, it may not actually be necessary. You might consider relaxing your requirement.

Consider this:

You must validate input data!

All you're deciding on is whether you're doing the validation at object creation time, or at object consumption time. Go's constructs generally make the latter easier (for both the coder, and the consumer of the data). If you truly must do validation at object creation time, then your only option is to use unexported types, and getter/setter methods for everything.

huangapple
  • 本文由 发表于 2017年4月11日 10:56:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/43336009.html
匿名

发表评论

匿名网友

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

确定