What the capstone puts together
This exercise combines the object-oriented pieces of Go in one small program: an interface, three structs that satisfy it, and a loop that treats them all alike. Go has no classes, so the pieces look a little different from other languages:
- Interface.
Animalis a contract: a type that has aspeak()method is anAnimal. - Structs.
Dog,CatandBirdeach hold anamefield. A struct is plain data; behaviour is attached to it with methods. - Methods with receivers.
func (this Dog) speak()attachesspeaktoDog. The receiver, written before the method name, plays the role ofselforthis. - Implicit satisfaction. No type says "I implement
Animal". Having the method is enough. - Polymorphism. A slice of type
[]Animalcan hold aDog, aCatand aBird, and callingspeak()on each element runs the right version.
The shape of the program is common in real Go code: a small interface at the top, concrete types below it, and the rest of the code written against the interface. Adding a fourth animal means writing one more struct and method; the loop stays as it is.
The task
The starter code declares Animal with a single method, speak(), and already contains mainFunction, which builds a slice of three animals named Rex, Whiskers and Tweety and calls speak() on each. What is missing are the three types. The expected output is:
Rex: Woof
Whiskers: Meow
Tweety: Tweet
Step by step
- Define
Dogas a struct with aname stringfield. The slice literal inmainFunctionwritesDog{name: "Rex"}, so the field must be calledname. The struct holds data only. - Give
Dogaspeakmethod. It prints the name and the sound withfmt.Printf("%s: Woof\n", this.name).Printfdoes not end the line by itself, so the format string needs the\n. With this method,DogsatisfiesAnimal. - Repeat for
Cat. Same field, same method name and signature, withMeowin the format string. - Repeat for
Bird, printingTweet. - Run the loop. Once all three types have
speak(), the literal[]Animal{...}compiles, andfor _, a := range animalscallsa.speak()on each element in order. Each call is dispatched to the method of the type stored ina.
The solution in Go
type Animal interface {
speak()
}
type Dog struct {
name string
}
func (this Dog) speak() {
fmt.Printf("%s: Woof\n", this.name)
}
type Cat struct {
name string
}
func (this Cat) speak() {
fmt.Printf("%s: Meow\n", this.name)
}
type Bird struct {
name string
}
func (this Bird) speak() {
fmt.Printf("%s: Tweet\n", this.name)
}
func mainFunction() {
animals := []Animal{Dog{name: "Rex"}, Cat{name: "Whiskers"}, Bird{name: "Tweety"}}
for _, a := range animals {
a.speak()
}
}
Details worth knowing
- The receiver name. The solution calls it
this, which reads naturally if you come from other languages, but it is an ordinary identifier in Go. Go code usually uses a short name taken from the type, such asdforDog. - Value receivers. The methods receive a copy of the struct. That is fine here because they only read
name. A method that changed a field would need a pointer receiver,func (d *Dog) ..., and then only*Dogwould satisfy the interface. - Keyed fields.
Dog{name: "Rex"}names the field. Fields left out take their zero value, soDog{}hasnameequal to"". - The repeated lines. Three nearly identical methods are normal in Go. The language prefers small, explicit pieces over inheritance.
Sharing behaviour: embedding, not inheritance
If every animal needs the same helper, Go offers struct embedding. A struct can contain another struct without giving the field a name, and the outer type gains the inner type's fields and methods:
type Sounder interface{ sound() string }
type Base struct{ name string }
func (b Base) say(s Sounder) {
fmt.Printf("%s: %s\n", b.name, s.sound())
}
type Cow struct{ Base }
func (c Cow) sound() string { return "Moo" }
c := Cow{Base{"Daisy"}}
c.say(c)
Daisy: Moo
Note the argument in c.say(c). Embedding is not inheritance: inside Base.say, the receiver is a Base, which knows nothing about the Cow around it, so it cannot call sound on its own. The outer value has to be passed in explicitly. This is why the capstone gives each type its own complete speak method.
Common mistakes
- Forgetting the field name. The loop builds
Dog{name: "Rex"}, so a struct with a field calledNameor no field at all does not compile. - Forgetting
\n. Without it, the three lines run together asRex: WoofWhiskers: MeowTweety: Tweet. - Misspelling the method. A method called
Speakorspeekdoes not satisfyAnimal, and the slice literal fails withdoes not implement Animal (missing method speak). Go is case sensitive. - Changing the signature. A
speak() stringmethod does not satisfy an interface asking forspeak(). - Looking for
implements. There is none; the compiler checks the methods where the value is used as anAnimal.
Key takeaways
- Structs hold the data, methods with receivers hold the behaviour, and interfaces describe the behaviour code depends on.
- A type satisfies an interface by having its methods, with no declaration.
- A slice of an interface type can mix concrete types, and each method call goes to the right one.
- Embedding shares fields and methods, but it does not give the shared code access to the outer type's overrides.