Object Oriented Programming in Swift
Learn how object oriented programming works in Swift by breaking things down into objects that can be inherited and composed from. By Cosmin Pupăză.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Object Oriented Programming in Swift
25 mins
Method Overloading
Add the following method to the Piano class right after the overridden play(_:) method:
func play(_ music: Music, usingPedals: Bool) -> String {
let preparedNotes = super.play(music)
if hasPedals && usingPedals {
return "Play piano notes \(preparedNotes) with pedals."
}
else {
return "Play piano notes \(preparedNotes) without pedals."
}
}
This overloads the play(_:) method to use pedals if usingPedals is true and the piano actually has pedals to use. It does not use the override keyword because it has a different parameter list. Swift uses the parameter list (aka signature) to determine which to use. You need to be careful with overloaded methods though because they have the potential to cause confusion. For example, the perform(_:) method always calls the play(_:) one, and will never call your specialized play(_:usingPedals:) one.
Replace play(_:), in Piano, with an implementation that calls your new pedal using version:
override func play(_ music: Music) -> String {
return play(music, usingPedals: hasPedals)
}
That’s it for the Piano class implementation. Time to create an actual piano instance, tune it and play some really cool music on it. :]
Instances
Add the following block of code at the end of the playground right after the Piano class declaration:
// 1
let piano = Piano(brand: "Yamaha", hasPedals: true)
piano.tune()
// 2
let music = Music(notes: ["C", "G", "F"])
piano.play(music, usingPedals: false)
// 3
piano.play(music)
// 4
Piano.whiteKeys
Piano.blackKeys
This is what’s going on here, step by step:
- You create a
pianoas an instance of thePianoclass andtuneit. Note that while types (classes) are always capitalized, instances are always all lowercase. Again, that’s convention. - You declare a
musicinstance of theMusicclass any play it on the piano with your special overload that lets you play the song without using the pedals. - You call the
Pianoclass version ofplay(_:)that always uses the pedals if it can. - The key counts are
staticconstant values inside thePianoclass, so you don’t need a specific instance to call them — you just use the class name prefix instead.
Now that you’ve got a taste of piano music, you can add some guitar solos to the mix.
Intermediate Abstract Base Classes
Add the Guitar class implementation at the end of the playground:
class Guitar: Instrument {
let stringGauge: String
init(brand: String, stringGauge: String = "medium") {
self.stringGauge = stringGauge
super.init(brand: brand)
}
}
This creates a new class Guitar that adds the idea of string gauge as a text String to the Instrument base class. Like Instrument, Guitar is considered an abstract type whose tune() and play(_:) methods need to be overridden in a subclass. This is why it is sometimes called a intermediate abstract base class.
That’s it for the Guitar class – you can add some really cool guitars now! Let’s do it! :]
Concrete Guitars
The first type of guitar you are going to create is an acoustic. Add the AcousticGuitar class to the end of the playground right after your Guitar class:
class AcousticGuitar: Guitar {
static let numberOfStrings = 6
static let fretCount = 20
override func tune() -> String {
return "Tune \(brand) acoustic with E A D G B E"
}
override func play(_ music: Music) -> String {
let preparedNotes = super.play(music)
return "Play folk tune on frets \(preparedNotes)."
}
}
All acoustic guitars have 6 strings and 20 frets, so you model the corresponding properties as static because they relate to all acoustic guitars. And they are constants since their values never change over time. The class doesn’t add any new stored properties of its own, so you don’t need to create an initializer, as it automatically inherits the initializer from its parent class, Guitar. Time to test out the guitar with a challenge!
[spoiler title="Acoustic Guitar"]Add the following test code at the bottom of the playground right after the AcousticGuitar class declaration:
[/spoiler]
[spoiler title="Acoustic Guitar"]Add the following test code at the bottom of the playground right after the AcousticGuitar class declaration:
let acousticGuitar = AcousticGuitar(brand: "Roland", stringGauge: "light")
acousticGuitar.tune()
acousticGuitar.play(music)
[/spoiler]
let acousticGuitar = AcousticGuitar(brand: "Roland", stringGauge: "light")
acousticGuitar.tune()
acousticGuitar.play(music)
It’s time to make some noise and play some loud music. You will need an amplifier! :]
Private
Acoustic guitars are great, but amplified ones are even cooler. Add the Amplifier class at the bottom of the playground to get the party started:
// 1
class Amplifier {
// 2
private var _volume: Int
// 3
private(set) var isOn: Bool
init() {
isOn = false
_volume = 0
}
// 4
func plugIn() {
isOn = true
}
func unplug() {
isOn = false
}
// 5
var volume: Int {
// 6
get {
return isOn ? _volume : 0
}
// 7
set {
_volume = min(max(newValue, 0), 10)
}
}
}
There’s quite a bit going on here, so lets break it down:
- You define the
Amplifierclass. This is also a root class, just likeInstrument. - The stored property
_volumeis markedprivateso that it can only be accessed inside of theAmplifierclass and is hidden away from outside users. The underscore at the beginning of the name emphasizes that it is a private implementation detail. Once again, this is merely a convention. But it’s good to follow conventions. :] - The stored property
isOncan be read by outside users but not written to. This is done withprivate(set). -
plugIn()andunplug()affect the state ofisOn. - The computed property named
volumewraps the private stored property_volume. - The getter drops the volume to 0 if it’s not plugged in.
- The volume will always be clamped to a certain value between 0 and 10 inside the setter. No setting the amp to 11.
The access control keyword private is extremely useful for hiding away complexity and protecting your class from invalid modifications. The fancy name for this is “protecting the invariant”. Invariance refers to truth that should always be preserved by an operation.