Protocol Oriented Programming: Prioritizing Flexibility and Reusability

Afonso Lucas
Level Up Coding
Published in
7 min readJul 12, 2023

--

When it comes to Object-Oriented Programming, dealing with the inheritance system is not always an easy task, especially when it comes to flexibility. At the 2015 WWDC, Apple introduced Swift, the first Protocol-Oriented Programming language. In this article, we will explore this new paradigm and understand how this approach can lead to more flexible and reusable codebases.

What you will learn:

  • Problems with Object-Oriented Programming inheritance;
  • What are protocols;
  • What is Protocol-Oriented Programming.

Problems with Object-Oriented Programming

The Object-Oriented Programming (OOP) paradigm is based on the concept of objects. OOP focuses on organizing code in a modular way, where our entire program is composed of objects, which are reusable units containing data and functionalities.

One of the main features of OOP for code reuse is inheritance, a concept that allows the creation of objects that inherit properties and functionalities from other objects.

Suppose we are developing a game and working on the characters. Initially, we know that a character can move and attack, so let’s create the base class for them:

class Character {
func move() {
// Basic move implementation
}

func attack() {
// Basic attack implementation
}
}

So far we’ve been informed that there will be two special types of character, archers and wizards:

class Archer: Character {
override func atack() {
// Shoot an arrow
}
}

class Wizard: Character {
override func atack() {
// Fireball!
}
}

Our characters are humans, they move the same way, so we only had to implement specific attacks, but we reused the base movement code. So far we have a class structure like this:

Until we discover that we will have to add a character who cannot attack, their role will be purely support with healing, which means they can no longer have the attack function. We could simply ignore the attack method and create this as a subclass of Character, but this design decision would surely cause problems later, so we decided to specialize our characters, all of them move, but not all attack, so we created a new diagram:

The complexity of the structure has already increased considerably — and we only added one new character. Because of this decision, we had to change the base class and, as a result, all the others:

class Character {
func move() {
// Basic move implementation
}
}

class AttackCharacters: Character {
func attack() {
// Basic attack implementation
}
}

Now our special characters:

class Archer: AttackCharacters {
override func atack() {
// Shoot an arrow
}
}

class Wizard: AttackCharacters {
override func atack() {
// Fireball!
}
}

class Healer: Character {
func heal() {
// I need cover here!!
}
}

The complexity of this code can get much worse, imagine having to implement orcs or undead, it would be a big headache to create a code structure that can separate characters by race and class, I would reconsider if all this work is really worth it in exchange for reusing a small part of the code, in addition we added code that we didn’t need before and a lot of complexity.

Despite inheritance being a means of code reuse, dealing with this concept can bring problems and add unnecessary complexity to the code. Some of the main problems are related to:

  1. Rigid Coupling: Inheritance can lead to rigid coupling between classes, where changes in one class can have a cascade effect on other related classes. Modifying the implementation of the base class can end up affecting its subclasses.
  2. Lack of Flexibility: Inheritance is a static relationship defined at compile time. Subclasses inherit the entire interface and implementation of their parent class, even if they only need a subset or a different behavior.
  3. Lack of Composition: Inheritance is about “is-a” relationships, where a subclass is a specialized type of its parent class. However, many cases require a “has-a” relationship, prioritizing composition over inheritance.
  4. Code Duplication: Inheritance can sometimes lead to code duplication if similar behaviors or properties need to be shared among several subclasses that are not part of the same inheritance hierarchy.

What are protocols?

In the Swift language, a protocol is a way to define a model of methods, properties, and other requirements that support a particular task or functionality. It’s similar to an interface, but with some additional features. In other words, we can use protocols both to describe what an object is as well as what it does. Protocols can be adopted by classes, structures, or enumerations. Any type that satisfies the requirements of a protocol is said to conform to that protocol.

1. Defining a Protocol

protocol Shape {

// Properties
var area: Double { get }
var perimeter: Double { get }

// Methods
func description() -> String
}

In this example, the Shape protocol defines requirements for geometric shapes. It requires conforming types to have an area computed property of type Double and a perimeter computed property of type Double. Additionally, the protocol specifies that the type must implement a method called description() that returns a String.

2. Conforming to a Protocol

First, we’ll create a Circle struct, which we want to conform to the protocol:

struct Circle {
let radius: Double
}

To conform to the Shape protocol, we need to implement the required properties and methods:

struct Circle: Shape {
let radius: Double

var area: Double {
return Double.pi * radius * radius
}

var perimeter: Double {
return 2 * Double.pi * radius
}

func description() -> String {
return "Circle with radius \(radius)"
}
}

Protocol-Oriented Programming

Protocol-Oriented Programming (POP) is a programming paradigm that emphasizes the use of protocols to design components. It encourages us to work with the composition of functionalities, where we define protocols and have types that conform to them, which is more flexible than class inheritance chains.

Going back to the previous example, let’s draw it in a protocol-oriented way. All of our characters can move, but not all things that move are characters, so let’s create specialized protocols for each ability:

protocol Movable {
func move()
}

protocol Attackable {
func attack()
}

protocol Healable {
func heal()
}

Now we can create our characters:

class Archer: Movable, Attackable {
func attack() {
// Shoot an arrow
}

func move() {
// Run!!
}
}

class Wizard: Movable, Attackable {
func attack() {
// Fireball!
}

func move() {
// Run!!
}
}

class Healer: Movable, Healable {
func heal() {
// I need cover here!!
}

func move() {
// Run!!
}
}

Ok, this way is more flexible, but I had to repeat the movement code for all classes, which did not happen in the inheritance example. No worries, we can have default implementations in protocols through extensions:

extension Movable {
func move() {
// Default Run!!
}
}

Now we can remove the move implementations:

class Archer: Movable, Attackable {
func attack() {
// Shoot an arrow
}
}

class Wizard: Movable, Attackable {
func attack() {
// Fireball!
}
}

class Healer: Movable, Healable {
func heal() {
// I need cover here!!
}
}

If we need a specific implementation, just add it to the class and the default implementation will be overridden:

class Healer: Movable, Healable {
func heal() {
// I need cover here!!
}

// Override default method "move"
func move() {
// Fly
}
}

Now we have a slightly different diagram, where structures conforms to behaviors defined by protocols:

Protocols are really powerful tools. With them, we can have truly flexible code when working with composition, while not losing the ability to work with inheritance, as protocols can inherit from other protocols.

Conclusion

Protocol-Oriented Programming certainly changes the way we work with objects, trading some of the rigidity of inheritance for the versatility of protocols. With this approach, we gain flexibility, modularity, and the ability to reuse our code.

I hope this article has been helpful. If you could help me, follow me on medium and clap the article, thanks! 🤠

Level Up Coding

Thanks for being a part of our community! Before you go:

🔔 Follow us: Twitter | LinkedIn | Newsletter

🚀👉 Join the Level Up talent collective and find an amazing job

--

--

 iOS Developer & Backend enthusiast - World is a giant software. Talk to me through port 443 🤠