Go Composition and Interfaces: Sharing Functionality Efficiently

Introduction

Welcome back! In this final lesson of our Go Structs Basics Revision course, we will explore how Go promotes code reuse and flexibility through composition and methods. While some languages rely heavily on inheritance, Go prefers composition to share functionality between types, enhancing code readability and maintainability.

In this lesson, we'll explore how Go achieves shared behavior with composition by embedding structs and using interfaces. Our lesson plan includes understanding composition, leveraging embedded structs for shared attributes, utilizing methods to capture behavior, and learning Go-specific idioms for initializing structs. Ready? Let's dive in!

Defining Composition

In Go, composition is favored over classical inheritance. Instead of inheriting from a base type, Go types can include or "embed" other types to reuse their functionality.

Here's an example using a struct named Vehicle and another struct named Car:

package main

import "fmt"

// Define the base struct 'Vehicle'
type Vehicle struct {
    Color string
    Brand string
}

// Define the struct 'Car' that embeds 'Vehicle'
type Car struct {
    Vehicle // Embedded Vehicle struct
    Doors   int
}

func main() {
    // Create an instance of 'Car' with an embedded 'Vehicle'
    myCar := Car{
        Vehicle: Vehicle{Color: "Red", Brand: "Toyota"},
        Doors:   4,
    }

    // Access embedded fields directly from 'Car'
    fmt.Println("Car Brand:", myCar.Brand)
    fmt.Println("Car Color:", myCar.Color)
    fmt.Println("Number of Doors:", myCar.Doors)
}

In this example, Car includes Vehicle as an embedded field. This allows Car to directly access Vehicle's fields (Color and Brand) without having to qualify them with the embedded struct's name (i.e., myCar.Vehicle.Color). This demonstrates one way Go facilitates attribute reuse using composition.

Attribute Inheritance

In Go, we achieve shared attributes through embedded structs, which allow one struct to include fields from another.

Consider this example using a struct named Artist and another struct named Musician:

package main

import "fmt"

// Define the base struct 'Artist'
type Artist struct {
    Name string
}

// Define the struct 'Musician' that embeds 'Artist'
type Musician struct {
    Artist    // Embedded Artist struct
    Instrument string
}

// Method to display the musician's details
func (m Musician) Display() {
    fmt.Printf("Name: %s\nInstrument: %s\n", m.Name, m.Instrument)
}

func main() {
    // Create an instance of 'Musician' with an embedded 'Artist'
    john := Musician{
        Artist:    Artist{Name: "John Lennon"},
        Instrument: "Guitar",
    }
    john.Display() // Call the method to display details
}

The Musician struct includes Artist as an embedded struct, allowing Musician to access fields originating from Artist (in this case, Name). The Display method illustrates how this struct pattern can express collective information using attributes from both the embedded and top-level structs.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal