Golang interface and common fields

Issue

I kinda newbee and have an architecture problem on go & interface:
I need to store info about Vehicle and assume it could be Car, Bus, Ambulance, Firetruck etc. So Vehicle is interface and every exact Vehicle type is struct, but all of them have some common fields (colour, number of wheels, seats, mufflers etc) and when I need to get something common I can go following ways:

  1. Don’t use interface at all and have one big struct with a lot of getters and setters checked every time "if vehicle type X can have field Z?". It makes those methods very hard to read and very easy to forget to check smthng.
  2. Perform type assert every time on every type to get exact field. So just to get colour I will need to write type switch for 10 lines.
  3. Add to interface getter for every common field:
type Vehicle interface {
    Colour() string
    Wheels() int
    Seats() int
    Mufflers() int
    ...
}

As I see it’s anti-pattern for "keep interface small" and will produce a lot of very similiar code (same method for every struct)

  1. Have some struct like CommonVehicle which store all common fields and all other vehicle type embed it and interface have only method return this CommonVehicle:
type Vehicle interface {
    Common() CommonVehicle
}

type CommonVehicle struct {
   // common fields
}

type Car struct {
   CommonVehicle
   // uncommon fields
}

// implementation for Vehicle interface

When I need to get colour I will do vehicle.Common().Colour. It looks clear on interface and types side, but it could mislead to call every time Common to get anything from Vehicle.

What is the best practice for this? Maybe I missed something and need to go some other way?

Solution

You are trying to have Inheritance which is not supported in Go (and some believe it is not a good pattern overall). Instead, you should use Composition (which is exactly what your #4 option suggests).

This blog post describes it perfectly

A different approach as a general design rule

instead of trying to define interfaces of objects, you should aim towards capabilities you wish your objects will be able to do.
This does not correlate 100% with your question but it will help with maintaining the pattern of small interfaces.

Let’s take a vehicle as an example: what can we do with a vehicle?

  • drive it
  • perhaps listen to music
  • sell it
  • be amazed of its beauty

Now that we know what we can do with a vehicle, we should have each of these actions as its own interface.
Why? – because it allows for the most granular description of any vehicle entity, without any assumptions about what specific implementations of a vehicle have.

So we have the following interfaces:

type Drivable interface{ Drive(float64, float64) } // drive to a latitude, longitude quordinate
type Listenable interface{ Listen() []byte } // Listen return a stream of bytes as the audio output
type Sellable interface { Sell(float64, string) } // sell for X amount of money to some person
type Describable interface { Describe() string }

Now with these 4 interfaces, we can create any type of vehicle we want:

  • Some vehicles won’t have a radio, so they probably shouldn’t implement the Listenable interface.
  • Some vehicles are police vehicles – the city owns them – so they are not really sellable
  • etc, etc…

The point is to have different capabilities and from those capabilities we should build our entities – and not vice-versa.

Answered By – Chaos Monkey

Answer Checked By – Robin (GoLangFix Admin)

Leave a Reply

Your email address will not be published.