In order to achieve composition, we need to take a look at type embedding in Go. Suppose we are trying to build a car. A car is composed of many small moving parts. We want to be able to reuse these small moving parts to build different type of cars.
Struct Embedding
First we have a turbo charged four cylinder engine.
typeTurboChargedstruct{ Horsepower int Torque int ChargerCount int FanSpeed float64}
We can define couple methods on the engine.
func(tc TurboCharged)ignite(){ fmt.Printf("4 cylinder is running, %d chargers are ready\n", tc.ChargerCount)}
Now let's build a car with this turbo charged engine by embedding it into a car struct.
typeCarstruct{TurboCharged}func(c Car)Start(){ c.ignite()}funcmain(){ c :=Car{TurboCharged{300,350,2,100.5}} c.Start()}
Output:
Notice that ignite is also embedded onto Car. I made it private so that starting an engine is abstracted away from user.
Interface Embedding
Now you may ask, what if I want a different engine like a naturally aspirated 6 cylinder? You can achieve it by embedding interfaces.
Change the embedded type to Engine
Now we can put whatever engine we like into our car.
type Engine interface {
ignite()
}
type NatAspirated struct {
Horsepower int
Torque int
}
func (na NatAspirated) ignite() {
fmt.Printf("6 cylinder is running, VROOOOMMMMM\n")
}
type Car struct {
Engine
}
func (c Car) Start() {
c.ignite()
}
func main() {
var c Car
c = Car{NatAspirated{400, 450}}
// OR
c = Car{TurboCharged{300, 305, 2, 100.5}}
c.Start()
}