Composition in Golang

Composition in Go refers to the practice of combining multiple struct types to form a new struct that inherits or embeds the properties and behavior of the constituent types. It allows for code reuse and modular design by promoting composition over inheritance.

Let’s look at an example to understand composition in Go:

package main

import "fmt"

type Engine struct {
    Power int
}

func (e Engine) Start() {
    fmt.Println("Engine started")
}

type Car struct {
    Model string
    Engine
}

func main() {
    c := Car{
        Model:  "Sedan",
        Engine: Engine{Power: 150},
    }

    fmt.Println("Car model:", c.Model)
    fmt.Println("Engine power:", c.Power)

    c.Start()
}

In this example, we have two struct types: Engine and Car. The Engine struct represents an engine with a Power field and a Start() method. The Car struct has a Model field and embeds an Engine struct.

By embedding the Engine struct within the Car struct, the Car struct gains access to all the fields and methods of the Engine struct. This is an example of composition.

In the main() function, we create a Car instance c using a struct literal. We initialize the Model field of the Car and embed an Engine instance with a Power of 150.

We can access the fields of the embedded Engine struct directly from the Car struct (c.Power). Similarly, we can call the Start() method of the embedded Engine struct using the Car struct (c.Start()).

The output of the program will be:

Car model: Sedan
Engine power: 150
Engine started

By using composition, the Car struct inherits the fields and methods of the embedded Engine struct, making them accessible and usable directly from the Car struct. This allows for code reuse and modular design without the complexity and limitations of inheritance.

Composition by embedding structs In Go

Composition by embedding structs in Go allows you to include one struct type within another struct type, enabling the embedding struct to inherit the fields and methods of the embedded struct. This concept is known as struct embedding, and it promotes code reuse and modularity.

Let’s take a look at an example to understand composition by embedding structs in Go:

package main

import "fmt"

type Engine struct {
    Power int
}

func (e Engine) Start() {
    fmt.Println("Engine started")
}

type Car struct {
    Model string
    Engine
}

func main() {
    c := Car{
        Model:  "Sedan",
        Engine: Engine{Power: 150},
    }

    fmt.Println("Car model:", c.Model)
    fmt.Println("Engine power:", c.Power)

    c.Start()
}

In this example, we have two struct types: Engine and Car. The Engine struct represents an engine with a Power field and a Start() method. The Car struct has a Model field and embeds an Engine struct.

By including the Engine struct within the Car struct using the Engine type as an anonymous field, the Car struct inherits all the fields and methods of the Engine struct.

In the main() function, we create a Car instance named c using a struct literal. We initialize the Model field of the Car and embed an Engine instance with a power of 150.

We can access the fields of the embedded Engine struct directly from the Car struct, such as c.Power. Similarly, we can call the Start() method of the embedded Engine struct using the Car struct, such as c.Start().

When we run the program, the output will be:

Car model: Sedan
Engine power: 150
Engine started

By using struct embedding, the Car struct gains the fields and methods of the embedded Engine struct, which allows us to reuse the functionality of the Engine within the Car struct. This promotes code reuse, modular design, and cleaner code organization by avoiding unnecessary duplication.

Embedding slice of structs in Go.

In Go, you can also embed a slice of structs within another struct by using a field of slice type. This allows you to include a collection of structs as part of a larger struct. Here’s an example:

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

type Team struct {
    Name     string
    Members  []Person
}

func main() {
    p1 := Person{Name: "John Doe", Age: 30}
    p2 := Person{Name: "Jane Smith", Age: 25}

    team := Team{
        Name: "Go Developers",
        Members: []Person{p1, p2},
    }

    fmt.Println("Team Name:", team.Name)
    fmt.Println("Team Members:")
    for _, member := range team.Members {
        fmt.Println("Name:", member.Name, "Age:", member.Age)
    }
}

In this example, we have two struct types: Person and Team. The Person struct represents an individual person with Name and Age fields. The Team struct has a Name field and embeds a slice of Person structs as Members.

In the main() function, we create two Person instances named p1 and p2. Then we create a Team instance named team with the name “Go Developers” and initialize the Members field with the slice of Person structs.

We can access the fields of the embedded slice of Person structs directly from the Team struct, such as team.Members. In the example, we iterate over the Members slice and print the name and age of each team member.

When we run the program, the output will be:

Team Name: Go Developers
Team Members:
Name: John Doe Age: 30
Name: Jane Smith Age: 25

By embedding a slice of structs, you can include a collection of structs as part of a larger struct, allowing for more complex data structures and relationships within your application. This approach promotes code organization and allows for easy access and manipulation of the embedded slice of structs.

Composition vs. Inheritance In Go.

In Go, composition and inheritance are two different approaches to achieving code reuse and structuring code. Here’s a comparison of composition and inheritance in Go:

Composition:

  • Composition promotes code reuse and modularity by combining multiple struct types to form a new struct.
  • It is achieved by embedding one struct within another, allowing the outer struct to inherit the fields and methods of the embedded struct.
  • Composition promotes a “has-a” relationship, where a struct contains another struct as a component.
  • Go favors composition over inheritance, as it provides more flexibility and avoids the complexities and limitations of inheritance hierarchies.
  • With composition, you can easily change and extend the behavior of a struct by embedding different components or swapping them out.

Inheritance:

  • Inheritance is a mechanism in traditional object-oriented programming languages where a class can inherit properties and behavior from a base class (superclass).
  • Inheritance promotes an “is-a” relationship, where a derived class (subclass) is a specialized version of the base class (superclass).
  • Go does not have built-in support for class-based inheritance. Instead, it encourages composition and interfaces for achieving code reuse and polymorphism.
  • While Go doesn’t directly support inheritance, you can achieve similar effects by embedding interfaces or embedding structs and selectively exposing their methods through wrapper methods.

In summary, Go favors composition over inheritance. Composition allows you to create complex structures by combining multiple structs, promotes code reuse, and provides flexibility and modularity. It avoids the complexities and limitations of inheritance hierarchies. Go’s approach with composition and interfaces promotes loose coupling, simpler code organization, and easier maintenance.