Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor with many arguments

Tags:

go

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?

like image 280
user7467314 Avatar asked Aug 31 '26 13:08

user7467314


2 Answers

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.

like image 53
Will Demaine Avatar answered Sep 03 '26 06:09

Will Demaine


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

like image 24
mkopriva Avatar answered Sep 03 '26 07:09

mkopriva



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!