Protocol Oriented Programming

There has been a lot of buzz ever since the 2015 WWDC talk on Protocol Oriented Programming. But the underlying principle has been around forever. Protocol is a feature provided by the programming language to facilitate polymorphism.

What is polymorphism?

There are many different kinds of polymorphisms as described in Wikipedia. I’ll focus on the kind that is the most important in Swift.

When I first learned about polymorphism in C++, it was at the function level. The goal is to avoid having to duplicate functions that take different types of parameters but do the same thing, making code reuse possible.

Duplication at the function level

Let’s take an example for a whirl.

It should be possible to call a professor or a student. But two call() functions are required because steve is a professor and tim is a student. Although they both have a phone number, they are of different types.

Inheritance at the function level

An early attempt to solve this classic problem in an object oriented programming language is through the use of class inheritance.

Professor and Student are changed to classes and inherit from Person. The shared attributes name and phone are now in the base class.

The call() function takes a Person object as its parameter. The function body can simply invoke person.phone.

You can invoke the call() function and pass in either a Professor or Student. The reason it works is because Professor and Student inherit from the same Person base class. At runtime, the object is cast to a Person object to be accessed in the body.

Inheritance at the entity level

So far, you’ve seen polymorphism at the function level. You avoid having to duplicate the call() function by using inheritance and base class pointer.

But the same principle can also be applied at the entity level.

I’ve moved the call() function to the Person class definition. You now invoke the call method on a Person object. After all, a person calls another person, right?

Class inheritance allows you to extract common behavior to a base class.

Protocol at the entity level

Over time, we’ve discovered inheritance comes with its own problems.

Two subclasses may share some common behaviors but not a lot. As a result, the base class becomes small and not very useful because you can’t put a lot of common behaviors in it. On the other hand, if you try to put more common behaviors in it, you’ll end up having to override the default behaviors in your subclasses.

Another problem occurs when your application grows. You need to model new objects. Expanding your class hierarchy to accommodate these new classes can also quickly turn into a nightmare. You may need to introduce new superclasses and regroup your subclasses. This means existing applications that use your class hierarchy will be completely broken when they are upgraded to use your new class hierarchy.

You often hear composition is preferred over inheritance. Protocols in Swift allow you to compose different behaviors in your classes.

(For an object oriented programming language such as C++, it allows a subclass to inherit from multiple superclasses. This is called multiple inheritance. And you can use it to simulate protocols. This mix-and-match concept is the same.)

The Callable protocol specifies that in order for a person to be callable, he needs to have a phone number and a call() method. At this point, it simply declares such a method. It doesn’t require the method to be defined just yet.

The Professor and Student classes conform to the Callable protocol by defining the phone variable and the call() method. They need to provide the method body to satisfy the protocol requirements.

The way you use the objects and methods remain the same.

Extension at the entity level

Protocols are great because they allow you to customize behaviors while not inheriting the hindrance from the parent.

But we’re back at our original problem with duplication. The call methods for both Professor and Student are the same. Wouldn’t it be nice to remove this duplication?

You can provide an extension to the Callable protocol to define the default behavior of the call() method. Professor and Student can simply use the default call() method if that’s sufficient. Or, they can define a customized version of the call() method.

A real life use case can be local vs long-distance calling. A default call() method can implement the details to make a local area call to another person. But a customized call() method can have extra instructions to process payment.

Generics at the function level

This is great for object oriented programming languages. But functional programming is the new buzz now. To me, functional programming isn’t new. It’s been around for ages. If preceded object oriented programming.

Let’s see how we can combine protocols and generics to achieve polymorphism back at the function level.

Here, the Callable protocol only requires a phone number. And we’ve changed Professor and Student back to structs. In Swift, structs can conform to protocols like classes. They conform to Callable by defining the phone variable.

Next, we define the call() function to take a generic parameter that confirms to the Callable protocol. In the method body, you can invoke callee.phone.

Finally, we’re back at solving our original problem – avoiding function duplication.

In conclusion, writing reusable code without duplication is at the heart of good software design. Pointers, inheritance, interfaces, protocols, and generics are features provided by programming languages to achieve the same goal – polymorphism. The Clean Swift architecture makes heavy use of protocols throughout its VIP cycle to isolate your source code dependencies.

The generics advantages (Updated)

Doug commented below and asked what the advantages are for using Swift generics for the call() function above. In this specific case, there’s none. I was just trying to illustrate how you could use generics with the simple Callable protocol to implement polymorphism in Swift.

But that wouldn’t be a very good example, isn’t it? So here’s an expanded version.

Your Callable protocol can be more complex. For example, if you want to allow a phone number to be a String or Int because you want people to be able to dial 123-456-7890 or 1234567890. You’ll then need to use associatedtype in your Callable protocol. The PhoneNumber associated type acts as a placeholder for the type of the phone number, which can be a string or integer.

Furthermore, you can also specify protocol conformance constraints for your associated type such that T.PhoneNumber conforms to the CustomDebugStringConvertible protocol. Your phone number can then be printed with dashes regardless if it’s a string or integer.

The non-generic version of the call() method doesn’t compile because the Callable protocol uses the associated type PhoneNumber. As a result, you’ll need to implement the call() method using generics.

Swift generics can be very powerful. But like Doug said, it shouldn’t be used unnecessarily.

References

Here are some references to polymorphism in different programming languages if you want to read more.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

4 Comments

  1. What’s the advantage of using Generics in the method func call(callee: T)?
    This can be written as:

    func call(callee: Callable)

    ….which seems more simple, and achieves the same thing?

      1. Thanks! It took this and played with it a bit. It finally sunk in. Even in this very simple example, if you defined the Callable protocol with just phone: String, or phone: Int, one of the two structs does not conform. With generics, 2 structs with different implementations of the same parameter can conform (easily) to the same protocol.

        Thanks for taking the time to explain further! Great example!

Leave a Comment

Your email address will not be published. Required fields are marked *