Applying Protocol-Oriented Programming in Development

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

Lesson 01: Introduction to Protocol-Oriented Programming

Demo 1

Episode complete

Play next episode

Next
Transcript

Demo

To really compare the differences between protocols and inheritance, it’s time to write some code! In this demo, you’ll implement the code for FamilyCar and see how to use the types in a simple app. You’ll then build the same functionality into a type using inheritance to compare the two different approaches.

First, open the starter playground from this lesson’s materials. Inside FamilyCar, implement move():

// 1
let travelingSpeed: Double
if speed < 0 {
  travelingSpeed = 0
} else if speed > maxSpeed {
  travelingSpeed = maxSpeed
} else {
  travelingSpeed = speed
}
// 2
let distance = travelingSpeed * duration
// 3
if direction == .backwards {
  totalDistanceTraveled = totalDistanceTraveled - distance
  print("Traveled -\(distance)")
} else {
  totalDistanceTraveled = totalDistanceTraveled + distance
  print("Traveled \(distance)")
}

This does the following:

  1. Works out the speed the car is traveling, ensuring it’s not less than 0 or greater than the maximum speed.
  2. Calculates the distance traveled by multiplying the speed by the duration.
  3. Adds the distance traveled to the total distance traveled — or subtracts it, if the car is traveling backwards.

Now that you’ve implemented move()’s functionality, you can see how to use the function. First, create an instance of FamilyCar to call the function on.

let familyCar = FamilyCar()

Then, call move() on the FamilyCar a couple of times and print the details to run through the implementation and see how to call the functions on your type:

familyCar.move(direction: .forward, duration: 15, speed: 30)
familyCar.move(direction: .forward, duration: 15, speed: 60)
print(familyCar.showDetails())

Run the playground; you’ll see the message appear in the console.

Next, implement move() in VehicleType. You can copy and paste the code from FamilyCar:

let travelingSpeed: Double
if speed < 0 {
  travelingSpeed = 0
} else if speed > maxSpeed {
  travelingSpeed = maxSpeed
} else {
  travelingSpeed = speed
}
// 2
let distance = travelingSpeed * duration
// 3
if direction == .backwards {
  totalDistanceTraveled = totalDistanceTraveled - distance
  print("Traveled -\(distance)")
} else {
  totalDistanceTraveled = totalDistanceTraveled + distance
  print("Traveled \(distance)")
}

You’ve now implemented the function for the inheritance option. Note that you’re implementing the code in the superclass; therefore, you don’t need to implement it in the subclass.

Next, create an instance of FamilyCarInheritance and call the same functions as the protocol implementation to see how to use it:

let familyCar2 = FamilyCarInheritance()
familyCar2.move(direction: .forward, duration: 15, speed: 30)
familyCar2.move(direction: .forward, duration: 15, speed: 60)
print(familyCar2.showDetails())

This has the same output as the protocol implementation. Up to this point, there’s no real difference.

One of the advantages of using protocols in Swift is you can leverage the compiler to check your code for you. At the bottom of the playground, create a new vehicle that conforms to the protocol:

class Truck: Vehicle {
    
}

See how you already get an error from the compiler because the type is missing the properties and functions that the protocol requires. Use Xcode’s Fix-It shortcut to add the stubs for you.

Next, implement showDetails():

"I am a truck"

Then, implement move(direction:duration:speed:). Again, you can copy and paste the code from FamilyCar:

let travelingSpeed: Double
if speed < 0 {
  travelingSpeed = 0
} else if speed > maxSpeed {
  travelingSpeed = maxSpeed
} else {
  travelingSpeed = speed
}
let distance = travelingSpeed * duration
if direction == .backwards {
  totalDistanceTraveled = totalDistanceTraveled - distance
  print("Traveled -\(distance)")
} else {
  totalDistanceTraveled = totalDistanceTraveled + distance
  print("Traveled \(distance)")
}

Finally, create an initializer:

init() {
  self.numberOfWheels = 6
  self.maxSpeed = 30
  self.totalDistanceTraveled = 0
}

The protocol defines numberOfWheels and maxSpeed as immutable, but you can still mutate them inside your type… which might make sense for your implementation. From the API perspective, it’s read-only.

To demonstrate this, create a Truck and set the number of wheels to 12:

let truck = Truck()
truck.numberOfWheels = 12

There’s nothing wrong with this code, and it compiles just fine. If you set the type of truck to be Vehicle, it will fail to compile because the API does not match what the protocol defines:

let truck: Vehicle = Truck()
truck.numberOfWheels = 12

Remove the type annotation so your playground compiles again.

let truck = Truck()
truck.numberOfWheels = 12

Finally, change totalDistanceTraveled to a let in Truck:

let totalDistanceTraveled: Double

This time, you do get an error because the protocol defines the API of Vehicle to have a mutable property called totalDistanceTraveled. This doesn’t exist in your new Truck implementation. Change your code back so it compiles.

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