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.