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
piano
as an instance of thePiano
class andtune
it. Note that while types (classes) are always capitalized, instances are always all lowercase. Again, that’s convention. - You declare a
music
instance of theMusic
class any play it on the piano with your special overload that lets you play the song without using the pedals. - You call the
Piano
class version ofplay(_:)
that always uses the pedals if it can. - The key counts are
static
constant values inside thePiano
class, 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
Amplifier
class. This is also a root class, just likeInstrument
. - The stored property
_volume
is markedprivate
so that it can only be accessed inside of theAmplifier
class 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
isOn
can be read by outside users but not written to. This is done withprivate(set)
. -
plugIn()
andunplug()
affect the state ofisOn
. - The computed property named
volume
wraps 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.