Introduction to Object-Oriented Programming

Oct 17 2023 · Swift 5.9, iOS 17, Xcode 15

Lesson 01: Basics of Object-Oriented Programming

Demo

Episode complete

Play next episode

Next
Transcript

In this demo, you’ll take a quick look at the difference between procedural programming and object-oriented programming. I’m using an Xcode playground and writing in Swift. If you’re following along, start up Xcode and open the module1-lesson1 playground in the starter folder.

This playground imports two Swift frameworks:

import PlaygroundSupport
import CoreLocation

PlaygroundSupport helps manage the execution of the playground page. In the next lesson, you’ll use it to show views, but all it does here is finishExecution(), right at the end:

PlaygroundPage.current.finishExecution()

CoreLocation provides the location data type used in the demo examples. And here are two locations you’ll use:

let melbourne = CLLocation(latitude: -37.840935, longitude: 144.946457)
let ballarat = CLLocation(latitude: -37.5622, longitude: 143.8503)

First up is an example of a procedural programming function that computes the travel time between two CLLocation values. I haven’t written the actual code, but it would compute the point-to-point distance between the from and to values and use some average speed, probably assuming the travel mode is driving.

Now suppose the programmer decides or is told to modify the function so it’s more accurate for other travel modes, like walking, cycling or public transport. Maybe add some parameters for average speed and actual distance:

func computeTravelTime(from: CLLocation,
                       to: CLLocation,
                       averageSpeed: Double,
                       actualDistance: Double) -> Double {
  actualDistance/averageSpeed
}

Making this change breaks every call to this function in the program and, wherever it’s called, you now need to precede it by a calculation of actualDistance. And you probably need to include some branching code to set these new parameter values for the different travel modes.

Later, you might want to fine-tune the driving calculation to allow for traffic level and the average time to find parking. But these values aren’t relevant to the other travel modes, so you’d need to call a different function.

Now, look at the TravelMode class. TravelMode is a very general concept that covers walking, cycling, driving and public transport.

class TravelMode: Identifiable {
  let id = UUID()

Identifiable is a standard library protocol that makes it easier to iterate over collections of TravelMode objects. Its only requirement is the id property, which must be a unique value for each instance, and UUID() takes care of this without any fuss. You don’t need to know what its actual value is — it’ll just do its job quietly.

There are two other properties:

private let mode: String
let averageSpeed: Double

mode is walking, cycling, driving or public transport. After you create a TravelMode object, the app’s code can’t access it. You’ll check this for yourself soon.

You supply the value of averageSpeed when you create a TravelMode object: It’s an estimate that depends on the mode and user- or time-specific factors like how fast the user can walk or cycle, or whether the driving is on city streets or highways.

You pass values for these two properties in the initializer:

init(mode: String, averageSpeed: Double) {
  self.mode = mode
  self.averageSpeed = averageSpeed
}

self. refers to the object itself. Here, it differentiates the object’s properties from the parameters, which have the same names.

Down below the class definition, instantiate TravelMode:

let audrey = TravelMode(mode: "walking", averageSpeed: 4.5)

That’s 4.5 km/hr.

Now, test what I said about not being able to access mode:

audrey.mode

Soon you see an error message: ‘mode’ is inaccessible due to ‘private’ protection level. This is an example of abstraction: Other parts of your app can interact with an object only through its public interface.

Comment out or delete the line audrey.mode.

Back in TravelMode, look at the two methods:

func actualDistance(_ from: CLLocation, _ to: CLLocation) -> Double {
  // use relevant map info for each specific travel mode
  fatalError("Implement this method for \(mode).")
}

func computeTravelTime(from: CLLocation, to: CLLocation) -> Double {
  actualDistance(from, to)/averageSpeed
}

computeTravelTime(from:to:) looks just like the original procedural programming function, but it uses actualDistance(), which uses data that’s specific to each travel mode, and the averageSpeed that was used to instantiate the TravelMode object. Everything you need for computeTravelTime(from:to:) is encapsulated in the object, and the app’s code just needs to provide the from and to locations.

actualDistance(_:_:) is a placeholder function. Having a method like this means you shouldn’t create instances of this class. Instead, you should define a subclass and override this method to suit objects of that type. It’s a kind of back-door way of creating an abstract class.

Try calling it with your TravelMode object:

audrey.actualDistance(melbourne, ballarat)

Click the run button to execute the playground.

There’s an error flag and, in the console, this message appears:

__lldb_expr_3/module1-lesson1.playground:27: Fatal error: Implement this method for walking.

If you don’t want your class to be “sort of abstract”, you could implement actualDistance(_:_:) to return the point-to-point distance — “as the crow flies”.

For now, comment out or delete this method call. If the error message sticks around, select, from the Product menu, Clear All Issues.

Leave actualDistance(_:_:) as a placeholder, because you’re about to create some subclasses!

class Walking: TravelMode {

}

Writing :TravelMode after the class name means Walking is-a TravelMode. It inherits all the properties and methods of TravelMode, so you don’t include them in its definition. The only thing you need to write is the method that isn’t implemented in TravelMode:

override func actualDistance(_ from: CLLocation, _ to: CLLocation) -> Double {
  // use map info about walking paths, low-traffic roads, hills
  42
}

42 is only a placeholder value — you have to return something.

Now, instantiate your subclass:

let tim = Walking(mode: "walking", averageSpeed: 6)

Tim is younger and taller than me, so I’m guessing he walks faster.

Call that pesky method:

tim.actualDistance(melbourne, ballarat)

Also call computeTravelTime(from:to:):

tim.computeTravelTime(from: melbourne, to: ballarat)

Run the playground. actualDistance is 42, so travel time is 7 hours.

Now, write one more subclass:

class Driving: TravelMode {
  override func actualDistance(_ from: CLLocation, _ to: CLLocation) -> Double {
    // use map info about roads, tollways
    57
  }
}

57’s another placeholder value, different from Walking’s 42.

Instantiate a Driving object:

let car = Driving(mode: "driving", averageSpeed: 50)

50 km/hr is about right for averageSpeed, as much of the distance is on a highway.

And compute its travel time:

let hours = car.computeTravelTime(from: melbourne, to: ballarat)

Run the playground. The travel time value actually isn’t far off, considering it’s using placeholder values, but it’s different from the Walking object’s travel time, because it’s using its own version of actualDistance and averageSpeed. That’s polymorphism in action!

So far, Driving works the same as Walking. How do you set it up to use traffic level and parking time? Instead of overriding computeTravelTime, you overload it: You add parameters to create a different function signature. A Driving object can call this version of computeTravelTime, but no other subclass type can.

func computeTravelTime(from: CLLocation,
                       to: CLLocation,
                       traffic: Double,
                       parking: Double) -> Double {
  actualDistance(from, to)/averageSpeed * traffic + parking
}

Try this out:

let minutes = 60 * car.computeTravelTime(
  from: melbourne,
  to: ballarat,
  traffic: 1.2,
  parking: 0.5)

Run the playground. As you’d expect, travel time is now more than the inherited method’s calculation.

Just check to see if tim can call this method: Start typing…

tim.c

Nope, only the TravelMode method appears in the menu. If you force it, by copy-pasting, you get an error flag: Extra arguments.

That ends this demo. Continue with the lesson for a summary.

See forum comments
Cinema mode Download course materials from Github
Previous: Instruction Next: Conclusion