SPONSORED ADS

You may also like

Golang composition over Inheritance all you need to know

Last Updated Jan 17, 2023

If you are familiar with Design Patterns, you are probably aware of the composition over inheritance or composite reuse principle. In fact, it is mentioned in the book's very first chapter. Don't worry if you don't know that principle. Go's composition features are so good, and since it doesn't support inheritance, you can't go wrong

Composition seems complex but it shouldn't be. If you put together some kinds of stuff to make a new one that is composition. Basically in golang, all you need to know is

  • put a struct in another struct
  • we can also put interface in another interface
  • interface in a struct ? yes why not

Enough chit chat Let's dive in.

golang embedding structs in struct

First, let's create a robot

type robot struct {
	name string
}

Give it a method. A robot should always know to say beep boop !

func (r *robot) say() {
	fmt.Printf("%s say beep boop!\n", r.name)
}

func main() {
	megatron := new(robot)
	megatron.name = "megatron"
	megatron.say() // ~> megatron say beep boop!
}

Later we hear about asimo, a humanoid robot created by Honda in 2000. Asimo is a robot but it can recognize moving objects. By embedding robot in asimo, all properties and method of robot are promoted to asimo

type asimo struct {
	robot // put robot struct in asimo struct
}

func (a *asimo) recognize() {
	// asimo struct can use name of robot struct
    fmt.Printf("%s can see stuff moving around\n", a.name) 
}

func main() {
	eva := new(asimo)
	eva.name = "eva"
	eva.say() // ~> eva say beep boop !
	eva.recognize() // eva can see stuff moving around
}

Whenever one struct field is embedded in another, Go gives us the option to access the embedded fields as if they were part of the outer struct. As a asimo, eva can access the properties and method of robot struct, she can call the underlying method directly eva.say() or eva.robot.say() both work the same.

However we can make eva.say() work different than eva.robot.say() by overide say method in asimo struct

// here we override the method say in asimo
// to provide a new implementation of the robot struct
func (a *asimo) say() {
	a.robot.say() // we can still access the method of robot struct
    fmt.Printf("%s also say it love wall·e \n", a.name) // and properties
}

you could also override properties, it works the same way.

golang embedding interface in interface

robot love beep and boop, let's create 2 interface beeper and booper. A beeper know how to beep and a booper know how awesome boop is

// beeper know how to beep
type beeper interface {
	beep()
}
// but booper only know how awesome to boop
type booper interface{
	boop()
}

Robot however know both beep and boop, we need an interface for that too, let's call it boopbeeper.

type boopbeeper interface{
	beep() // i can beep
	boop() // and boop at the same time!
}

we are duplicating the same method declarations in several places, oops! It seems like a code smell. Don't worry, we can easily fix it by putting interface in another interface, just like this

type boopbeeper interface{
	beeper
	booper
}

now change any method signature in beeper interface will also update boopbeeper interface. much better.

golang embedding interface in struct

Just like when we put a struct inside a struct, when we put an interface inside a struct, we give the outer struct more freedom. Instead of hardcoding the inner struct, we now let any struct that implements the interface be embedded.

Let's start with a simple example: asimo is a struct that embeds beeper interface.

type beeper interface{
	beep()
}

type asimo struct {
	beeper
}

Then a robot struct that implement the beeper interface

type robot struct{
	name string
}

func (r robot) beep() {
	fmt.Printf("%s say beep !")
}

because robot implements the interface beeper, it can be embed in asimo.

eva := asimo{
	robot{name: "eva"},
}
eva.beep() // ~> eva say beep!

But not robot, any struct that implements beeper can be embedded in asimo. That's the reason for embedding the interface in struct.

Takeaway

Composition is more than just the mechanics of embedding types. It's a way of thinking that we can use to make better APIs and to put together bigger programs from smaller ones. It all starts with declaring and implementing types that only do one thing. When programs are built with composition in mind, they have a better chance of being able to grow and change as needs change. Also, they are much easier to read and understand.

References

Hi there. Nodeepshit is a hobby website built to provide free information. There are no chargers to use the website.

If you enjoy our tutorials and examples, please consider supporting us with a cup of beer, we'll use the funds to create additional excellent tutorials.

If you don't want or unable to make a small donation please don't worry - carry on reading and enjoying the website as we explore more tutorials. Have a wonderful day!