29. Composition and interfaces — Homework solutions

The .go solution files are in exercises/29/homework/solutions/.

Problem 1 — Vehicle and Car

Problem. A car that gets describe from its embedded vehicle.

How to think about it. The types are there already. The literal fills the field named after the embedded type, and the promoted method does the rest.

Worked solution.

package main

import "fmt"

type vehicle struct {
    name string
}

func (v vehicle) describe() {
    fmt.Printf("%s is a vehicle.\n", v.name)
}

type car struct {
    vehicle
}

func main() {
    beetle := car{vehicle: vehicle{name: "Beetle"}}
    beetle.describe() // Beetle is a vehicle.
}

Common mistakes.

  • Writing car{name: "Beetle"}. Promotion works for reading and calling, but not in a literal. Go stops with unknown field name in struct literal of type car.

Problem 2 — Replace the method

Problem. car's describe says "car" instead of "vehicle".

How to think about it. A method with the same name on the outer type wins. c.name still works in it, because the field is promoted.

Worked solution.

package main

import "fmt"

type vehicle struct {
    name string
}

func (v vehicle) describe() {
    fmt.Printf("%s is a vehicle.\n", v.name)
}

type car struct {
    vehicle
}

func (c car) describe() {
    fmt.Printf("%s is a car.\n", c.name)
}

func main() {
    cart := vehicle{name: "Cart"}
    beetle := car{vehicle: vehicle{name: "Beetle"}}

    cart.describe()   // Cart is a vehicle.
    beetle.describe() // Beetle is a car.
}

A vehicle value uses its own method. A car finds its own first.

Problem 3 — Extend the embedded method

Problem. car's describe runs the embedded version, then adds a line.

How to think about it. Go through the field, c.vehicle, to reach the inner method.

Worked solution.

package main

import "fmt"

type vehicle struct {
    name string
}

func (v vehicle) describe() {
    fmt.Printf("%s is a vehicle.\n", v.name)
}

type car struct {
    vehicle
}

func (c car) describe() {
    c.vehicle.describe()
    fmt.Println("  It has four wheels.")
}

func main() {
    beetle := car{vehicle: vehicle{name: "Beetle"}}
    beetle.describe()
}

Output:

Beetle is a vehicle.
  It has four wheels.

Common mistakes.

  • Calling c.describe() inside describe. That calls the same method again, and again, until the program crashes with a stack overflow. Use c.vehicle.describe().

Challenge — Two kinds

Problem. A bird and a fish in one slice, each with its own move.

How to think about it. A []animal cannot hold them, because a bird is not an animal. What they share is a method, so the slice's type is an interface with that method. Neither type needs to mention mover.

Worked solution.

package main

import "fmt"

type mover interface {
    move()
}

type animal struct {
    name string
}

type bird struct {
    animal
}

func (b bird) move() {
    fmt.Printf("%s flies.\n", b.name)
}

type fish struct {
    animal
}

func (f fish) move() {
    fmt.Printf("%s swims.\n", f.name)
}

func main() {
    creatures := []mover{
        bird{animal: animal{name: "Robin"}},
        fish{animal: animal{name: "Nemo"}},
    }

    for _, c := range creatures {
        c.move()
    }
}

Output:

Robin flies.
Nemo swims.

Embedding shares the name field. The interface shares the way of calling them. They do different jobs, and they often appear together.

Common mistakes.

  • Giving animal a move and expecting a method of animal to reach the bird's move. An embedded type does not know what contains it.

Done?

You can share code with embedding, and treat different types alike with interfaces. The last chapter of Part 6, Designing a small type, asks how to make a type safe and pleasant to use.