TL ; DR:
Put it all together: an interface, three concrete structs, and a polymorphic loop that dispatches by runtime type. This is the shape of nearly every real OOP design.
type Animal interface {
speak() // each implementor provides its own
}
type Dog struct{ name string }
func (d Dog) speak() {
fmt.Printf("%s: Woof\n", d.name)
}
// Cat and Bird are written the same way
// animals := []Animal{Dog{name: "Rex"}, ...}
// for _, a := range animals { a.speak() }
Full lesson:
This problem stitches together everything you've learned in this chapter:
- Interfaces — three concrete structs share a common interface.
- Struct initialization — each struct sets a name field when created.
- Interface methods — the interface declares
speak()with no body; each struct must provide one, and it satisfies the interface just by doing so. - Receivers — each
speak()method uses its struct'snamefield to print the name and the animal's sound. - Polymorphism — a slice typed as
[]Animalholds a Dog, a Cat, and a Bird;a.speak()runs the right method at runtime.
The shape of this code is the shape of nearly every real OOP design you'll see — a small contract at the top (the interface), concrete variants below, and the rest of the program written against the abstract type. New variants get added without touching the consumer code.
Your task
The Animal interface is already defined, and mainFunction already builds a slice holding Rex, Whiskers and Tweety and calls speak() on each. Add three structs, each with a name field and a speak() method that prints the name, a colon and the sound — Dog ("Woof"), Cat ("Meow"), and Bird ("Tweet").
Expected output:
Rex: Woof
Whiskers: Meow
Tweety: TweetFree plan: 30 tutor messages. PRO is unlimited.
