Object-oriented programming without inheritance
Go doesn’t have inheritance – instead composition, embedding and interfaces support code reuse and polymorphism.
- Object-oriented programming with inheritance
- Code reuse by composition
- Code reuse by embedding
- Polymorphism and dynamic dispatch with interfaces
Object-oriented programming with inheritance
Inheritance in traditional object-oriented languages offers three features in one.
When a Dog
inherits from an Animal
- the
Dog
class reuses code from theAnimal
class, - a variable
x
of typeAnimal
can refer to either aDog
or anAnimal
, x.Eat()
will choose anEat
method based on what type of objectx
refers to.
In object-oriented lingo, these features are known as code reuse, polymorphism and dynamic dispatch.
All of these are available in Go, using separate constructs:
- composition and embedding provide code reuse,
- interfaces take care of polymorphism and dynamic dispatch.
Code reuse by composition
Don't worry about type hierarchies when starting a new Go project –
it's easy to introduce polymorphism and dynamic dispatch later on.
If a Dog
needs some or all of the functionality of an Animal
,
simply use composition.
type Animal struct {
// …
}
type Dog struct {
beast Animal
// …
}
This gives you full freedom to use the Animal
part of your Dog
as needed.
Yes, it’s that simple.
Code reuse by embedding
If the Dog
class inherits the exact behavior of an Animal
,
this approach can result in some tedious coding.
type Animal struct {
// …
}
func (a *Animal) Eat() { … }
func (a *Animal) Sleep() { … }
func (a *Animal) Breed() { … }
type Dog struct {
beast Animal
// …
}
func (a *Dog) Eat() { a.beast.Eat() }
func (a *Dog) Sleep() { a.beast.Sleep() }
func (a *Dog) Breed() { a.beast.Breed() }
This code pattern is known as delegation.
Go uses embedding for situations like this.
The declaration of the Dog
struct and it’s three methods can be reduced to:
type Dog struct {
Animal
// …
}
Polymorphism and dynamic dispatch with interfaces
Keep your interfaces short, and introduce them only when needed.
Further down the road your project might have grown to include more animals. At this point you can introduce polymorphism and dynamic dispatch using interfaces.
If you need to put all your pets to sleep, you can define a Sleeper
interface.
type Sleeper interface {
Sleep()
}
func main() {
pets := []Sleeper{new(Cat), new(Dog)}
for _, x := range pets {
x.Sleep()
}
}
No explicit declaration is required by the Cat
and Dog
types.
Any type that provides the methods named in an interface
may be treated as an implementation.
When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.
–James Whitcomb Riley
What about constructors?
See Constructors deconstructed for best practices on how to set up new data structures in Go.