Skip to content

Polymorphism and protocol extensions

Allow me to start using a big word: cyclomatic complexity

Cyclo-what? Illustrate that with an example!

Let me illustrate a typical example of cyclomatic complexity. Imagine you have some entities similar to these:

Now, imagine we need to render a table view containing Images, Movies, and TextMessages. Notice how each one of those entities provides a description of itself (the data that will need to be rendered) of a completely different type. In plain English, the table cells will need to display a UIImage if the object to render is of type Image, a NSURL if the object to render is of type Movie, and a String if the object to render is of type TextMessage.

So, the obvious implementation, the one that first comes to my mind, the one I have seen in a thousand different projects in the good old Objective-C days (yes, that was sarcasm), would be declaring a method typing the parameter as id, and then cast and check the type of whatever is passed. Translated to Swift, it would look like this:

Well, that was bad in the good old Objective-C days (sarcasm again), but now, in the bright Swift present, code like that is just unacceptable.

What wait, why is that bad?

Here is where the cyclo-thingy kicks in.

That method is very apparently very simple, but in reality is very complex. Or, to be more accurate, it has the potential to become very complex, very fast.

First, as soon as we start to create different logical branches, we are starting to add different behaviours to somethign that, in fact, is just one single behaviour (in this case, rendering data in a cell). Since we are not dealing with a single behaviour anymore, it is very likely that the behavious in each one of those logical branches will start diverging more and more as time goes by.

But that is not the only reason. As soon as we start creating different logical branches, and each branch starts behaving differenly, the code starts to become more difficult to understand, which makes it more difficult to maintain.

Also, when the code is more difficult to understand, specially because it does multiple, very different things, it is easier to make mistakes. Never understimate the importance of this. This, in my experience, is the first source of bugs in every single codebase I have ever touched.

It took me years to figure it out, but once I got it, I decided to make this my only non-negotiable rule when it comes to writing code:

The Single Resposibility Principle applies to every single task in the daily practice of a software engineer, from organizing code in files, to structs, classes, methods, functions, or any other construct.

Again, let me be clear about this: to me this rule is non-negotiable. Sowftare engineering is all about trade-offs except when it comes to the Single Responsibility Principle.

One file should contain only one abstraction (a struct, a class, an enumeration, a protocol, you name it), one method should only do one thing (either render a movie, or render an image, but never, ever, render a movie or an image), one class should do just one thing, one module should deal with only one high-level concern, and so on.

Now that we got that out of the way, let’s look again at the implementation of the Cell class, because there is another violation of the SOLID principles: the Cell class is not open for extension and closed for modification. If we want to render another type (say, Audio), and since Cell is already rendering three different tyes it wouldn’t be that surprising if we had to add yet another one, we would need to edit the source code of the Cell class. And we’d need to edit it to add yet more logical branches, more complexity.

In other words, we would need to edit to make even more brittle. Not good.

So, where do protocol extensions fit into all of this?

Well, protocol extensions make composing behaviours very easy.

Now, it could be argued that the solution I am going to discuss is not the only possible solution. And that would be true. And we have already discussed an alternative solution to a problem very similar to this, by relying on plain-old polymorphism. So, one thing we could do here would be having a bunch of Cells, all of them implementing a protocol, or all of them subclasses of a base Cell class. However, I believe there is a neater way to implement this.

So, let’s start again with the same entities:

Now, let’s declare a protocol, and declare the render method in it:

The next step would be declaring a Cell class:

Notice how this class is actually empty, does not provide any behaviour at the moment. Nowm let’s start composing behaviours in extensions and protocol extensions. The first extension would make the Cell class implement the Populable protocol, and provide a “default behaviour” for it:

To be honest, having to provide a “default behaviour” is the only part of this solution I don’t like. As you can see, I typed the parameter as Cell, assuming a cell won’t have to render itself, and that way, I am providing a defaul tbehaviour that I do not expect to be necessary. And, again, that makes me feel uncomfortable.

Anyway, now I can start providing the rendering behaviour that I expect from the Cell, in separate extensions, one for each type of data I want to render:

Notice how Swift’s type inferrence makes the final code clean of any extra type information: it just works:

Now, here is the actual beauty of this solution. If I want to render a new type, all I have to do is add an extension to the Populable protocol with the logc necessary to deal with that specific type:

And, as added bonus, Xcode, with the help of the Swift compiler, will provide me extra documentation, in the form of code hints, about what types can be rendered by an instance of Cell:

codeHint-supported-types

Recap!

In object-oriented programming, polymorphism (from the Greek meaning “having multiple forms”) is the characteristic of being able to assign a different meaning or usage to something in different contexts – specifically, to allow an entity such as a variable, a function, or an object to have more than one form.

By relying in strict typing, the single responsibility principle, favouring composition over inheritance, and using protocol extensions in our advantage, we can implement very robust solutions to complex problems, with code compliant with the Single Responsibility Principle, and therefore simple to read, understand, and maintain. And, as an added benefit, easy to test.

7 Comments

    • sexysoftwareengineer sexysoftwareengineer

      Thanks!

  1. hauk hauk

    Stripping away the extension hop, what you have isn’t that basically:

    final class Cell: UITableViewCell {
    func render(data: Image) {
    print(data.image)
    }
    func render(data: Movie) {
    print(data.url)
    }
    func render(data: TextMessage) {
    print(data.message)
    }
    }

    What is gained by using extension instead of the above? The default dummy implementation wasn’t very convincing.

    • Pat G Pat G

      There are couple of advantages of using extension over the said approach :
      a) If a different kind of cell needs the similar functionality, one has to copy and paste the required methods (or instead of making this class as final, we can extend this class. However it is advantageous to go with composition over inheritance for several reasons.
      b) Also if another kind of component needs this functionality, one can get this functionality without adding these functions to that component (by just extending this component with <> protocol.
      Like author pointed out that he is not comfortable with the default implementation. Of course one can always add fatalError(”….”).

  2. Shane Wu Shane Wu

    Great article.

    However, I feel quite confused when actually adopting this approach.
    Let’s say Cell has an UIImageView instance to render the Image struct.
    Populable protocol don’t know how to assign the UIImage to Cell’s UIImageView.
    Is there something that I misunderstand?

  3. What an awesome article. It wasn’t long ago that I’ve been meaning to implement polymorphism using Swift and was wondering if that’s possible or not!

    Like you mentioned, the default behavior and data: Cell will make other readers of the code use too much of their brain cells. Wonder if there’s any other way that this can be avoided.

  4. Othman Othman

    Hi, nice article.
    But I can’t get it , how will you update the cell from the extension of Populable, !!,
    e.g. in the TextMessage where we add cell.textlabel.text = textMessage.text !?

    Thnx.

Leave a Reply

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