Embedding instead of inheritance
| Embed a struct | |
| Promoted methods | |
| Shadow a method | |
| Compose, do not inherit |
Embed a struct
Embedding places one struct inside another without a field name. The outer struct then reads the inner fields as if they were its own, which is the Go replacement for simple reuse through inheritance.
package main
import "fmt"
type Person struct {
Name string
}
type Employee struct {
Person
ID int
}
func main() {
e := Employee{Person: Person{Name: "Ann"}, ID: 7}
fmt.Println(e.Name, e.ID)
}
Employee has no Name field of its own, yet e.Name reaches into the embedded Person. When two embedded structs expose the same field name, spell out the path, as in e.Person.Name.
Promoted methods
Methods of the embedded struct are promoted to the outer struct automatically. Callers use the short path, while the method still runs with the inner value as its receiver.
package main
import "fmt"
type Person struct {
Name string
}
func (p Person) Greet() string {
return "hello, I am " + p.Name
}
type Employee struct {
Person
ID int
}
func main() {
e := Employee{Person: Person{Name: "Ann"}, ID: 7}
fmt.Println(e.Greet())
}
Greet is defined once on Person, but e.Greet works directly on the employee. Promotion also carries interfaces along: an Employee satisfies every interface its embedded Person satisfies.
Shadow a method
When the outer struct defines a method with the same name, the outer one wins and the inner one is shadowed. The hidden version stays reachable through the explicit embedded field, so you can wrap it instead of losing it.
package main
import "fmt"
type Person struct {
Name string
}
func (p Person) Greet() string {
return "hello, I am " + p.Name
}
type Employee struct {
Person
ID int
}
func (e Employee) Greet() string {
return fmt.Sprintf("employee %d says: %s", e.ID, e.Person.Greet())
}
func main() {
e := Employee{Person: Person{Name: "Ann"}, ID: 7}
fmt.Println(e.Greet())
fmt.Println(e.Person.Greet())
}
The outer Greet shadows the promoted one, then reuses it through e.Person.Greet. This pattern mirrors overriding with a call to super, minus the class hierarchy.
Compose, do not inherit
Embedding shines when you assemble behavior from small parts instead of growing a deep tree. Give each struct one job, embed the pieces you need, and let the outer type stay thin.
package main
import "fmt"
type Logger struct{}
func (Logger) Log(msg string) {
fmt.Println("log:", msg)
}
type Store struct {
items int
}
func (s *Store) Add() {
s.items++
}
type Service struct {
Logger
Store
}
func main() {
svc := Service{}
svc.Log("start")
svc.Add()
fmt.Println(svc.items)
}
Service combines a Logger and a Store with two short lines and no base class. Prefer this flat composition: it keeps each piece testable and every dependency visible.
Next: Generics
Article author: Arthur Isaev