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 withunknown 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()insidedescribe. That calls the same method again, and again, until the program crashes with a stack overflow. Usec.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
animalamoveand expecting a method ofanimalto reach the bird'smove. 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.