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ă.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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:

  1. You create a piano as an instance of the Piano class and tune it. Note that while types (classes) are always capitalized, instances are always all lowercase. Again, that’s convention.
  2. You declare a music instance of the Music class any play it on the piano with your special overload that lets you play the song without using the pedals.
  3. You call the Piano class version of play(_:) that always uses the pedals if it can.
  4. The key counts are static constant values inside the Piano 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.

Note: You will notice that there’s nothing stopping you creating an instance of a class that is abstract. This is true, and is a limitation to Swift. Some languages allow you to specifically state that a class is abstract and that you can’t create an instance of it.

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]

Challenge: Define a Roland-brand acoustic guitar. Tune, and play it.

[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:

  1. You define the Amplifier class. This is also a root class, just like Instrument.
  2. The stored property _volume is marked private so that it can only be accessed inside of the Amplifier 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. :]
  3. The stored property isOn can be read by outside users but not written to. This is done with private(set).
  4. plugIn() and unplug() affect the state of isOn.
  5. The computed property named volume wraps the private stored property _volume.
  6. The getter drops the volume to 0 if it’s not plugged in.
  7. 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.

Cosmin Pupăză

Contributors

Cosmin Pupăză

Author

Ray Fix

Tech Editor and Team Lead

Chris Belanger

Editor

Matt Galloway

Final Pass Editor

Over 300 content creators. Join our team.