What the capstone combines
This exercise puts the chapter's ideas into one small program: a parent class, three subclasses, and a loop that treats them all as the parent type. It uses four ideas:
- Inheritance.
Dog,CatandBirdall inherit fromAnimal. - Initializers. Each animal takes a name, and the parent stores it.
- Overriding. The parent has a placeholder
sound()that each subclass replaces. - Polymorphism. An array typed
[Animal]holds all three, and one call per element picks the right sound.
The shape is common: a small contract at the top, concrete variants below, and the consuming code written against the parent type. Adding a fourth animal later does not change the loop.
The parent class
The starter code gives you Animal:
class Animal {
var name: String
init(_ name: String) { self.name = name }
func sound() -> String { return "" }
func speak() {
print("\(name): \(sound())")
}
}
Notice the split of work. sound() is the part that varies, so the parent gives it a neutral default, an empty string. speak() is the part that stays the same, so it is written once: it builds the line name: sound by calling sound(). Inside speak(), the call sound() means self.sound(), and Swift runs the version that belongs to the object's actual class. This is the template pattern: shared code in the parent calls a method the subclasses fill in.
The initializer is written init(_ name: String), with an underscore, so callers pass the name without a label: Dog("Rex"), not Dog(name: "Rex").
In Swift this parent is only "abstract by convention". Swift does not force a subclass to override sound(); one that forgets simply inherits the empty string, and its speak() prints the name followed by a colon and a space.
Step by step
- Write the three subclasses. Each one is
class Name: Animalwith one method,override func sound() -> String, returning"Woof","Meow"or"Tweet". They override onlysound(). They do not touchspeak()or the storedname. - Skip the initializers. A subclass that adds no stored properties and defines no initializers of its own inherits all of the parent's designated initializers. That is why
Dog("Rex")works with no code inDogfor it. - Build the array in order. Inside
mainFunction, declarelet animals: [Animal] = [Dog("Rex"), Cat("Whiskers"), Bird("Tweety")]. The order is the order of the output. - Loop and speak. Write
for a in animals { a.speak() }. Each pass calls the inheritedspeak(), which calls the rightsound().
The solution in Swift
class Animal {
var name: String
init(_ name: String) { self.name = name }
func sound() -> String { return "" }
func speak() {
print("\(name): \(sound())")
}
}
class Dog: Animal {
override func sound() -> String { return "Woof" }
}
class Cat: Animal {
override func sound() -> String { return "Meow" }
}
class Bird: Animal {
override func sound() -> String { return "Tweet" }
}
class Solution {
func mainFunction() {
let animals: [Animal] = [Dog("Rex"), Cat("Whiskers"), Bird("Tweety")]
for a in animals {
a.speak()
}
}
}
Running mainFunction() prints:
Rex: Woof
Whiskers: Meow
Tweety: Tweet
Swift details that matter
- String interpolation.
"\(name): \(sound())"inserts the value of any expression between\(and), including a method call. - Extending the hierarchy. A
Cowsubclass with its ownsound()returning"Moo"works asCow("Bessie").speak(), which printsBessie: Moo, and fits in the same array without changes to the loop. - When a subclass needs its own data, it must write an initializer. It sets its own properties first and then calls
super.init(name):
class Parrot: Animal {
var word: String
init(_ name: String, word: String) {
self.word = word
super.init(name)
}
override func sound() -> String { return word }
}
Parrot("Polly", word: "Hello").speak() prints Polly: Hello. Without the init, the class would not compile, because word has no initial value.
Common mistakes
- Forgetting
override. Writingfunc sound()inDogis a compile error that says anoverridekeyword is required. - Overriding
speak()instead ofsound(). It works, but each subclass then repeats the formatting. The point of the design is that the line format lives in one place. - Wrong order in the array. The output follows the array, so Rex, Whiskers and Tweety must be in that order.
- Using labels on the constructor call. The initializer's first parameter has an underscore, so
Dog(name: "Rex")does not compile. - Calling
super.initfirst in a subclass with its own properties. Swift requires the subclass's own properties to be set beforesuper.initis called.
Key takeaways
- Put shared behavior in the parent and the varying part in an overridable method.
- Subclasses inherit the parent's initializers when they add no stored properties of their own.
- An array typed with the parent class lets one loop work with every subclass.
- Swift picks the overridden method from the object's real class at run time.
