Composition over Inheritance

From my understanding, there is no constructor in Go, since Go does not advocate the concept of Object-Oriented Programming. Go prefers the idea of composition, which is more flexible than inheritance. Also, this is how C works, and Go is a language that is inspired by C.

How to construct structs?

My personal ranking of these 3:

Functional Options > Direct Field Access > “Constructors”

0. Setting

Suppose we have this Pineapple struct, with 3 fields:

  • from where pineapple is produced
  • weight weight of pineapple
  • price price of pineapple

1. Direct Field Access

The most straightforward way to construct a struct is to directly access the fields of the struct.

Struct Definition

package pineapple

// define pineapple struct
type Pineapple struct {
	from   string
	weight int64
	price  float64
}

// "Constructor" for Pineapple
func NewPineapple() *Pineapple {
	// default values for Pineapple
	return &Pineapple{
		from:   "Hawaii",
		weight: 1000,
		price:  10.0,
	}
}

How to use

package main

func main() {
	p := pineapple.NewPineapple()
	fmt.Printf("Pineapple from %s, weight %d, price %.2f\n", p.From, p.Weight, p.Price)
	p.From = "USA"
	p.Weight = 150
	p.Price = 15.0
	fmt.Printf("Pineapple from %s, weight %d, price %.2f\n", p.From, p.Weight, p.Price)
}

We will get result:

Pineapple from Hawaii, weight 1000, price 10.00
Pineapple from USA, weight 150, price 15.00

Pros

  1. Simple and straightforward.
  2. Slightly Flexible: imagine, we need to add a new filed color to the struct, we don’t need to change that much, just add the default value of color to the NewPineapple function and it is done.
  3. Readable: it is very easy to understand what is happening.

Cons

  1. All fields have to be public. This is a huge problem.
  2. Mutability. In this way, the constructed pineapple is mutable.
  3. Not thread-safe, as it is mutable.

2. “Constructors”

The traditional OOP way, not a good fit for this scenario.

Struct Definition

package pineapple

type PineConstructorApple struct {
	from   string
	weight int64
	price  float64
}

func New(price float64, weight int64) *PineConstructorApple {
	return &PineConstructorApple{
		from:   "Hawaii",
		weight: weight,
		price:  price,
	}
}

func NewWithFrom(from string, price float64, weight int64) *PineConstructorApple {
	return &PineConstructorApple{
		from:   from,
		weight: weight,
		price:  price,
	}
}

How to use

func main() {
	p := pineapple.NewWithFrom("Hawaii", 10.0, 1000)
	fmt.Printf("Pineapple %+v\n", p)
}

Pros

  1. Immutable, since we haven’t put setters there.

Cons

  1. Exponential complexity: imagine we have 5 fields, we need to have 2^5 = 32 constructors.
  2. Inflexible: imagine we need to add a new field color, how many constructors more do we need to add?

3. Functional Options

The most flexible way to construct a struct in Go.

Struct Definition

package pineapple

type PineWithApple struct {
	from   string
	weight int64
	price  float64
}

type PineWithAppleOption func(*PineWithApple)

func WithFrom(from string) PineWithAppleOption {
	return func(opts *PineWithApple) {
		opts.from = from
	}
}

func WithWeight(weight int64) PineWithAppleOption {
	return func(opts *PineWithApple) {
		opts.weight = weight
	}
}

func WithPrice(price float64) PineWithAppleOption {
	return func(opts *PineWithApple) {
		opts.price = price
	}
}

func NewPineWithApple(opts ...PineWithAppleOption) *PineWithApple {
	// default values for PineWithApple
	options := PineWithApple{
		from:   "Hawaii",
		weight: 1000,
		price:  10.0,
	}
	for _, opt := range opts {
		opt(&options)
	}
	return &options
}

How to use

func main() {
    p := pineapple.NewPineWithApple(
        pineapple.WithFrom("Taiwan"),
        pineapple.WithWeight(1234),
    )
    fmt.Printf("Pineapple: %+v\n", p)
}

Pros

  1. Simplicity: it is very easy to understand what is happening.
  2. Flexible: imagine we need to add a new field color, we just need to add a new WithColor function and it is done.
  3. Immutable: cannot change the constructed pineapple.
  4. Thread-safe: as it is immutable.

Cons

  1. Need some functional programming understanding.