Understanding Interfaces in Go

Let's just get right into it.

Understanding Interfaces

Think of interfaces as a set of behaviours. When someone or something implements this interface they define this set of behaviours on itself. For example, let's consider an interface Organism. Now let's say, we expect anything that is an organism to perform certain actions or behaviours such as breathe() and move() or eat().

This is what an interface would be, a set of behaviours (or functions) that we expect an entity to be able to perform (or a type to be able to call these functions).

What are interfaces?

So again, what are interfaces? They are an abstract type, that defines a set of function signatures that need to be implemented by any concrete type that wants to satisfy that interface. Why would any type want to satisfy an interface? We would come to that.

Abstract and concrete types?

Concrete types, simply put are types that can be instantiated, meaning a value of that type can be created and used. For example, int, string or even struct. I can define a struct as a new type, create an object for it and use it.

Whereas abstract types, can't be instantiated! Interfaces are abstract types. They serve as a basis for other types. Other types can implement an interface, and they can be sent as an argument to any other function which expects an instance of that interface.

Why Interfaces?

When your type, implements an interface, it can be used anywhere, where you would expect an interface. For example, let's say a mouse type satisfies our organism interface. So wherever I expect an instance of organism, mouse can be passed. Why would I need an organism? Maybe I have a function that gives free food say freeFood()! Now I would only want an object, that can perform the eat function to be passed there.

I could also have defined the function separately for mouse and dog but then I would have to write the same function twice. What we can do, we can leave the respective implementation of the eating method to the organism and we just expect someone who can eat. It would execute the freefood function.

Let us take another example along with the code this time. Let's first create three structs. One for a dog, one for nonVegetarian and one for vegetarian.

type dog struct {}
type nonVegetarian struct{}
type vegetarian struct{}

Now let's define an interface, this interface would be used to define all the people who can eat meat.

type canEatMeat interface {
    eatMeat()
}

Any type that would have an implementation of eatMeat can be said to be an instance of canEatMeat.

func (d dog) eatMeat() {
    fmt.Println("Dog is eating meat... Bark")
}

func (nv nonVegetarian) eatMeat() {
    fmt.Println("Non-vegetarian is eating meat... Yummy")
}

Now, I have a shop where I sell free meat. But I would want only those people to come to my shop who can eat Meat.

func comeAndEatMeat(o canEatMeat) {
    o.eatMeat()
}

As you can see, I could define an interface and anyone that implements it can use my function. If any new type comes, and it has that function it can easily come and use this function with no problem.

If instead, I had defined it on a type, only that type would be able to execute it.

Now for the main part.

func main() {
    myDog := dog{}
    myNonVeg := nonVegetarian{}
    myVeg := vegetarian{}

    comeAndEatMeat(myDog)        // Output: Dog is eating meat... Bark
    comeAndEatMeat(myNonVeg)     // Output: Non-vegetarian is eating meat... Yummy
    comeAndEatMeat(myVeg)        // Compilation error: vegetarian does not implement canEatMeat
}

I hoped this article helped you.

0
Subscribe to my newsletter

Read articles from Ujjawal Srivastava directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ujjawal Srivastava
Ujjawal Srivastava