1. Technology

Google Go Tutorial Nine on Interfaces

By

If you've come here first, start with the first Golang Tutorial One or Golang tutorial two on arrays, tutorial three on slices and tutorial four on pointers and structs.

I can still remember finding interfaces very confusing when I first came across them back in the late 90s so here's my explanation of them. Let's start with familiar code:

In an earlier tutorial on function methods, I explained how to add functions to structs and other types.

Don't know anything about Go? Here is an overview of Go.

To remind you about methods here's some code from that tutorial.

type dog struct {
  name string
  isabitch bool
}

func (d dog) bark() {
  fmt.Println(d.name,"Goes: 'Woof, Woof!'")
}

It's a struct and a method of "object dog" and this code uses it:

func main() {
  fido := dog {"Fido", false }
  fido.bark()
}

When run it outputs:

Fido Goes: 'Woof, Woof!'

An interface applies to every type and just specifies the set of methods defined for that type. It's not actual code but a specification (usually called a contract) of the methods needed to make the interface work.

If you want to implement a particular interface in code then you must provide a working copy of every method in the interface.

If we were to define an interface for dog (remember it can be for any type), it would look something like this.

type doggy interface {
  bark()
}

This means that any type that has a bark() method actually implements doggy. It's not necessary to explicitly state this, that's different to some other languages like Java which requires you to state in code that a particular class implements a particular interface.

Just put that to one side for a minute and read on. Now let's define another similar type called puppy. We'll also define a bark() method for puppy so it implements doggy as well.

type puppy struct {
    name    string
    age    int
}

func (p puppy) bark() {
    fmt.Println(p.name, "Goes: 'Squeak, Squeak!'")
}

So now both our dog and puppy types have a bark() method and that both dog and puppy implement the doggy interface. Let's build our own little pack with a dog and a puppy and iterate through the pack, calling bark() on each pack member. Here's the full code:

package main

import "fmt"

type dog struct {
        name   string
        isabitch   bool
}

func (d dog) bark() {
        fmt.Println(d.name, "Goes: 'Woof, Woof!'")
}

type puppy struct {
        name   string
        age   int
}

func (p puppy) bark() {
        fmt.Println(p.name, "Goes: 'Squeak, Squeak!'")
}

type doggy interface {
        bark()
}


func main() {
        fido := dog{"Fido", false}
        pip := puppy{"Pip",5}
        pack := [...]doggy{fido, pip}
        for _,d := range pack {
           fmt.Println(doggy(d))
           d.bark()
        }
}

This creates fido a dog and pip a puppy. This statement:

pack := [...]doggy{fido, pip}

Creates an array with two elements. You could equally use [2] like this below, but [...] just allocates one big enough to hold the specified elements. It's always the right size!

pack := [2]doggy{fido, pip}

The for loop iterates through the list accessing Fido then Pip, printing them out using the interface doggy(d). The output from this is:

{Fido false}
Fido Goes: 'Woof, Woof!'
{Pip 5}
Pip Goes: 'Squeak, Squeak!'

Remember that this array holds items which implement the doggy interface, so we can access these variables using the doggy interface type. This is important and explains why interfaces are such a big deal in Go.

This doesn't convert the underlying type to doggy. Fido is still a dog and pip a puppy, but doggy(fido) provides access to the methods that were defined in doggy and implemented in dog, In this case the method is fido.bark().

You could just as easily define a mammal interface with a method move(). If you added a move method for dog then you would be able to access mammal(fido) and call the move() method. You might have a cat type that implements move() but not bark(). So you couldn't use doggy ( on any instances of cat but you could use mammal.

Implementing Multiple Interfaces

A type can satisfy multiple interfaces as this example shows. In this example, I've created a cat type with a move() methods, added a move method to the dog type and added a mammal interface that specifies a move() method. So both cat and dog implement mammal as well as dog implementing doggy.

Here's the full code:

package main

import "fmt"

type dog struct {
        name    string
        isabitch    bool
}

func (d dog) bark() {
        fmt.Println(d.name, "Goes: 'Woof, Woof!'")
}

func (d dog) move() {
        fmt.Println(d.name, "Moves noisily")
}

type cat struct {
        name    string
        age    int
}

func (c cat) move() {
        fmt.Println(c.name, "Walks very delicately")
}

type doggy interface {
        bark()
}

type mammal interface {
        move()
}

func main() {
        fido := dog{"Fido", false}
        max := cat{"Max", 5}
        mammal1 := mammal(fido)
        mammal1.move()
        fido.bark()
        mammal2 := mammal(max)
        mammal2.move()
        fmt.Println(mammal1)
        fmt.Println(mammal2)
}

Because the interface restricts you to the specified methods, in the code above mammal1 can't call bark() only move() even though the dog type can bark.

In the next Golang tutorial I'll look a bit more at interfaces, there's still more to learn.

  1. About.com
  2. Technology
  3. C / C++ / C#
  4. All about Google Go
  5. Google Go Tutorial Nine on Interfaces

©2014 About.com. All rights reserved.