How to reuse code in GoLang in the absence of interfaces or function overriding?

149 Views Asked by At

I would like to know how GoLang handles code reuse without inheritances and function overrides. Below is an example of what I am facing now. I have a code base that handles POSTing an array of an object (e.g. Apple), everything is already there: handler, service, repository, all the functions, and I need to do the same for a new object which is slightly different from previous object (e.g. Orange).

Obviously I will have to do a bunch of refactoring to avoid code duplication but don't really know how and would appreciate any help!

models.go

package project

type Fruit struct {
  Property1 int64
  Property2 int64
  Property3 int64
  Property4 int64
  Property5 int64
}

type Apple struct {
  Colour int64
  Fruit
}

type Orange struct {
  Fruit
}


apples.go

package handler

func (h Handler) ApplesPost(apples []project.Apples) {
  err := validateApples(apples)
  // more code
}

func validateApples(apples []project.Apples) error {
  if len(apples) == 0 {
    // throw err
  }

  for _, apple := range apples {
    // do a ton of validations (one liners, function calls, etc.) on each property, including colour 
  }

  return nil
}

oranges.go

package handler

func (h Handler) OrangesPost(oranges []project.Oranges) {
  err := validateOranges(oranges)
  // more code
}

func validateOranges(oranges []project.Oranges) error {
  if len(oranges) == 0 {
    // throw err
  }

  for _, orange := range oranges {
    // do a ton of validations on all properties, 
    // pretty much exactly same as Apples validation function
    // except for colour validation
  }

  return nil
}
1

There are 1 best solutions below

1
Sepehr On

Well Go does have interfaces and if you use them your code will be something like the following.

Your handler will accept a slice of the defined interface instead of concrete types.

type Validatable interface {
    Validate() error
}

func FruitsPost(fruits []Validatable) {
    for _, f := range fruits {
        err := f.Validate()
        fmt.Println(err) // handle the error however you desire
    }
    // more code
}

Then you need to define Validate() on your types

func (a Apple) Validate() error {
    // do a ton of validations (one liners, function calls, etc.) on each property, including colour
    return nil
}


func (o Orange) Validate() error {
    // do a ton of validations (one liners, function calls, etc.) on each property, including colour
    return nil
}

But you won't be able to call your handler like FruitsPost([]Apple{}) (read this to see why). So You'll need a helper functions like the following to cast your slice of Apple to a slice of Validatable

func CastToFruits[T Validatable](fruits []T) []Validatable {
    result := []Validatable{}
    for _, f := range fruits {
        result = append(result, f)
    }

    return result
}