Ok, so you want to use polymorphism in Go, but don’t like interfaces? Read on…
First, let’s see what we want to do:
var dog, duck *Animal
dog = NewDog("fido")
duck = NewDuck("donald")
fmt.Println(dog.makeNoise())
// fido says woof!
fmt.Println(duck.makeNoise())
// donald says quack!
Here, dog
and duck
are of the same type (*Animal
). Each variable is instantiated with a different constructor, and they have different behaviours when the same method makeNoise
is called.
Normally, this use case is exactly what we use interfaces for, but we don’t want life to be that simple.
Let’s see how we can make this work:
TLDR; view the full code here
type Animal struct {
makeNoiseFn func(*Animal) string
name string
legs int
}
the Animal
struct contains name
and legs
attributes, as well as a mkeNoiseFn
attribute, which is actually a function, which takes in a pointer to Animal
and returns a string.
func (a *Animal) makeNoise() string {
return a.makeNoiseFn(a)
}
The makeNoise
method is actually just a wrapper, which calls the respective animals makenoiseFn
, providing the pointer to the animal itself as its argument.
func NewDog(name string) *Animal {
return &Animal{
makeNoiseFn: func(a *Animal) string {
return a.name + " says woof!"
},
legs: 4,
name: name,
}
}
func NewDuck(name string) *Animal {
return &Animal{
makeNoiseFn: func(a *Animal) string {
return a.name + " says quack!"
},
legs: 4,
name: name,
}
}
Now, all we have to do to have the same type exhibit different behaviour, is assign a different function as its makeNoiseFn
attribute. Now, the makeNoise
method secretly calls a different function depending on whether the animal is a dog, or a duck.
Should I do this?
No!
This post was intended to show you what you can do, not what you should. If you need to implement polymorphism, interfaces are a much better way to do it. Here’s what this code would look like if we used interfaces:
type Animal interface {
makeNoise() string
}
type Dog struct {
name string
legs int
}
func (d *Dog) makeNoise() string {
return d.name + " says woof!"
}
type Duck struct {
name string
legs int
}
func (d *Duck) makeNoise() string {
return d.name + " says quack!"
}
func NewDog(name string) Animal {
return &Dog{
legs: 4,
name: name,
}
}
func NewDuck(name string) Animal {
return &Duck{
legs: 4,
name: name,
}
}
func main() {
var dog, duck Animal
dog = NewDog("fido")
duck = NewDuck("donald")
fmt.Println(dog.makeNoise())
// fido says woof!
fmt.Println(duck.makeNoise())
// donald says quack!
}