Interfaces

Interfaces define a set of methods that must be implemented for a struct to satisfy the interface. They are used as a form of generic programming in Go, which does not support generics, yet.

Like structs, interfaces can embed other interfaces, and a struct must satisfy all methods of the embedded interface and the parent interface. Let's look at a few examples.

A simple interface:

package main

import "fmt"

// A Being can return its first and last name
type Being interface {
    GetFirstName() string
    GetLastName() string
}

// An Person is a human
type Person struct {
    first       string
    last        string
    age         int
    nationality string
}

// GetFirstName get the first name of a Person
func (p *Person) GetFirstName() string {
    return p.first
}

// GetLastName get the first name of a Person
func (p *Person) GetLastName() string {
    return p.last
}

// A Dog is a type of animal
type Dog struct {
    name  string
    breed string
}

// GetFirstName get the first name of a Dog
func (d *Dog) GetFirstName() string {
    return d.name
}

// GetLastName get the first name of a Dog
func (d *Dog) GetLastName() string {
    // structs may implement an interface however they see fit
    return ""
}

// GetFullName returns the full name for a Being
func GetFullName(b Being) string {
    // Passing a Being means we can contruct the fullname for any type that
    // implements GetFirstName and GetLastName
    return fmt.Sprintf("%s %s", b.GetFirstName(), b.GetLastName())
}

func main() {
    p := &Person{
        first:       "Ada",
        last:        "Lovelace",
        age:         36,
        nationality: "British",
    }

    d := &Dog{
        name:  "Spot",
        breed: "Corgi",
    }

    fmt.Println(GetFullName(p)) // prints "Ada Lovelace"
    fmt.Println(GetFullName(d)) // prints "Spot "
}

An interface with an embedded interface:

package main

import "fmt"

// Aged is something that has an age
type Aged interface {
    GetAge() int
}

// A Being can return its first and last name
type Being interface {
    Aged
    GetFirstName() string
    GetLastName() string
}

// An Person is a human
type Person struct {
    first       string
    last        string
    age         int
    nationality string
}

// GetFirstName get the first name of a Person
func (p *Person) GetFirstName() string {
    return p.first
}

// GetLastName get the first name of a Person
func (p *Person) GetLastName() string {
    return p.last
}

// GetAge gets the age of a Person
func (p *Person) GetAge() int {
    return p.age
}

// A Dog is a type of animal
type Dog struct {
    name  string
    breed string
    age   int
}

// GetFirstName get the first name of a Dog
func (d *Dog) GetFirstName() string {
    return d.name
}

// GetLastName get the first name of a Dog
func (d *Dog) GetLastName() string {
    return ""
}

// GetAge gets the age of a Dog
func (d *Dog) GetAge() int {
    return d.age
}

// GetFullName returns the full name for a Being
func GetFullName(b Being) string {
    return fmt.Sprintf("%s %s", b.GetFirstName(), b.GetLastName())
}

// GetFullNameAndAge returns the full name and age for a Being
func GetFullNameAndAge(b Being) string {
    return fmt.Sprintf("%s %s: %d", b.GetFirstName(), b.GetLastName(), b.GetAge())
}

// blank identifiers are useful for ensuring a type satisfies an interface at
// compile time
var _ Being = &Person{}
var _ Being = &Dog{}

func main() {
    p := &Person{
        first:       "Ada",
        last:        "Lovelace",
        age:         36,
        nationality: "British",
    }

    d := &Dog{
        name:  "Spot",
        breed: "Corgi",
        age:   2,
    }

    fmt.Println(GetFullNameAndAge(p)) // prints "Ada Lovelace: 36"
    fmt.Println(GetFullNameAndAge(d)) // prints "Spot : 2"
}