Go Interfaces Tutorial

In this Go tutorial we learn how to implement functions for our structs with interfaces.

We learn how to create interfaces and the abstract methods inside them, how to implement interfaces and how to access methods that are defined and linked.

Lastly, we discuss implementing multiple interfaces.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What is an interface

An interface allows us to create functions that are connected to structs. They contain a set of abstract methods that do not have a body with logic.

When we connect the interface to the struct, it can define its own logic for the method. This way, we force developers to follow our design, but allow them to implement their own logic.

When the struct implements the interface, it links to it, creating a has-a relationship. For example, a car has-a steering system, a player has-a weapon.

How to define an interface

We define an interface almost like a struct, but with the interface keyword.

Syntax:
type interface_name interface {

    // methods
}

Once we’ve defined the interface, we can add abstract methods. Remember that an abstract method doesn’t define a body with logic.

Other than that, it looks just like a regular function.

Syntax:
type interface_name interface {

    // abstract methods
    method_name(parameters) return_type
}
Example:
package main

func main() {}

type Flyable interface {

    // abstract method
    fly()
}

In the example above, we create a flyable interface with a single method called fly(). In this case, the method will simply print a message to the console, so it doesn’t have any parameters or return type.

How to implement an interface

When a struct implements an interface, it defines the logic of one or more of the methods available in that interface.

A method implementation looks similar to a function definition, except it has an extra parameter list in the header to link to the struct.

Syntax:
func (struct_object_name Struct_name) method_name(parameters) {

    // logic
}

The extra parameter list accepts one argument, the struct that we want to link to the method.

Example:
package main

import "fmt"

func main() {}

// interface
type Flyable interface {
    fly()
}

// struct
type Sparrow struct {}

// implement method for Sparrow struct
func (s Sparrow) fly() {

    fmt.Println("Sparrow flies at a speed of 10")
}

In the example above, we link the Sparrow struct to this particular implementation of the fly() method.

We can do this for multiple structs.

Example:
package main

import "fmt"

func main() {}

// interface
type Flyable interface {
    fly()
}

// structs
type Sparrow struct {}
type Plane struct {}

// implement method for Sparrow struct
func (s Sparrow) fly() {

    fmt.Println("Sparrow flies at a speed of 10")
}

// implement method for Plane struct
func (p Plane) fly() {

    fmt.Println("Plane flies at a speed of 2000")
}

In the example above, we added another struct called Plane. We also created an implementation of the fly() method for it.

How to call interface methods

If a method has been implemented for a struct, we can access it in the same way as we would a normal struct property.

Example:
package main

import "fmt"

func main() {

    // struct objects
    var s1 Sparrow
    var p1 Plane

    // call methods
    s1.fly()
    p1.fly()
}

// interface
type Flyable interface {
    fly()
}

// structs
type Sparrow struct {}
type Plane struct {}

// implement method for Sparrow struct
func (s Sparrow) fly() {

    fmt.Println("Sparrow flies at a speed of 10")
}

// implement method for Plane struct
func (p Plane) fly() {

    fmt.Println("Plane flies at a speed of 2000")
}

In the example above, we create two structs and call the methods linked to them with dot notation.

How to implement multiple interfaces

The great thing about interfaces is that we can implement more than one of them.

As an example, we can add a hoppable interface and let only the Sparrow implement it.

Example:
package main

import "fmt"

func main() {

    // struct objects
    var s1 Sparrow
    var p1 Plane

    // call methods
    s1.fly()
    s1.hop()
    p1.fly()
}

// interface
type Flyable interface {
    fly()
}
type Hoppable interface {
    hop()
}

// structs
type Sparrow struct {}
type Plane struct {}

// implement method for Sparrow struct
func (s Sparrow) fly() {

    fmt.Println("Sparrow flies at a speed of 10")
}
func (s Sparrow) hop() {

    fmt.Println("Sparrow hops at 1.2 hops per second")
}

// implement method for Plane struct
func (p Plane) fly() {

    fmt.Println("Plane flies at a speed of 2000")
}

The plane can’t hop, so it doesn’t need to implement it. However, if we had a bunny, it could implement something in the Hoppable interface, but not in the Flyable interface.

The benefit to using interfaces is that the application is loosely coupled. There aren’t any complex class inheritance hierarchies that can be abused or need constant updating.

Summary: Points to remember

  • An interface is a way for developers to link functions to structs.
  • An interface contains one or more abstract methods (method without body or logic).
  • Methods are similar to functions except they include an extra parameter list that links to a specific struct.
  • Methods that have been implemented are accessed the same way we would access a struct property, through an object, with dot notation.
  • Go allows us to implement more than one interface, that’s to say, we can link as many interfaces and methods to a struct as we want.