{ Soham Kamani }

About • Blog • Github • Twitter

Polymorphism in Go - without interfaces 🦆

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!
}

Like what I write? Join my mailing list, and I'll let you know whenever I write another post

Comments

Soham Kamani

Written by Soham Kamani, an author,and a full-stack developer who has extensive experience in the JavaScript ecosystem, and building large scale applications in Go. He is an open source enthusiast and an avid blogger. You should follow him on Twitter