Better Programming

Advice for programmers.

Follow publication

Swift: Favor Composition Over Inheritance. The BaseViewController case.

Alberto Salas
Better Programming
Published in
5 min readOct 4, 2019
Photo by Max Nelson on Unsplash
Photo by Max Nelson on Unsplash

In a past article, I used the OOP principle favor composition over inheritance as one of the reasons for avoiding the use of protocol default implementations in Swift. I didn’t want to go into detail because it would’ve been too long.

In this article, I’m going to use a familiar example for showing how we use composition over inheritance and why, when the goal is reusing code, it is a better approach.

BaseViewController

One of the most popular uses of inheritance for reusing code I’ve ever seen in an iOS project, is to have a BaseViewController. Every UIViewController in the project subclasses this BaseViewController, so they get shared behaviors, functionalities, components, dependencies, etc.

Something similar to this:

Ok, so what’s the problem here?

From BaseViewController to BaseViewContainer

BaseViewController quickly becomes a container for methods that are used (or will be potentially used) by two or more subclasses. This is hard to maintain and it breaks the SOLID’s single responsibility principle.

Every module or class should have responsibility for a single part of the functionality provided by the software.

Coupling

BaseViewController properties and methods/functionalities are not necessarily used by every UIViewController subclass. That means that subclasses are probably depending on code that they don’t need or use. You can see an example in functionality1 and functionality2.

According to OOP principles: You should not depend on methods that you don’t use.

With composition, you could use Inversion Of Control and Interface Segregation by defining and conforming small protocols. However, inheritance doesn’t allow you to do the same.

Encapsulation

Encapsulation refers to the idea that objects should manage their own behavior and state, so that their collaborators need not concern themselves with the object’s inner workings.

There are different ways in which BaseViewController breaks encapsulation:

  • BaseViewController ↔︎ Subclasses (I): If any BaseViewController subclass is allowed to access members inherited from it, changes in BaseViewController may require maintenance of the subclasses as well.
  • BaseViewController ↔︎ Subclasses (II): If any BaseViewController subclass is allowed to override members inherited from it (as long as they are non-final), overrides of BaseViewController methods might change its behavior.
  • ChildViewControllers ↔︎ Clients: Since Swift doesn’t provide a protected accessor for subclasses as Java does, BaseViewController shared methods and properties need to be public or internal. Subclasses inherit those public/internal members with the same visibility. Assuming that some of those members are supposed to be private, any client has public access to their inner workings.

Flexibility

Firstly, BaseViewController methods/functionalities can not be changed or injected at runtime as it could be done by using composition. This is because UIViewControllers depend on concrete implementations, not abstractions.

And secondly, those methods/functionalities can not be reused in a class that is not a UIViewController, unless you want to use a UIViewController as a dependency for that 🤯.

Unit Testing

Assuming that you’re writing tests for your code, you might want to inject and mock all dependencies for unit testing. It should be pretty clear at this point that subclassing BaseViewController will not allow you to do so with inherited methods/functionalities.

Common Header, Footer, or Background

The intention of this article is going through some reasons why you should favor composition over inheritance when it comes to share code.

But you might think you could still use BaseViewController just for setting some common Header, Footer, or Background in several UIViewController without duplicating code.

I think you will eventually fall into some of the previous issues for something you could achieve with another approach.

If you are sharing Headers, Footers, or Background from different UIViewController you could think on using ChildViewControllers so you decouple the container from you actual UIViewController. This way each component has its own responsibility and, additionally, your UIViewController could be injected into different containers.

Inheritance Semantics

I left this topic for the end because it is more conceptual than practical and it might be arguable. So please, take it as an additional and subjective reason.

Class inheritance, or subtyping if you want, in this case, is a mechanism that establishes an is-a relationship between two classes.

Regardless of the correctness and completeness of this diagram, here we can see that every Cat is a Mammal, and every Mammal is a VertebrateAnimal. In this taxonomy, every Class defines the properties and methods that belong to the concept it represents.

In the case of BaseViewController, given that it’s probably not going to be used as an actual UIViewController, we can’t say we have an is-a relationship. BaseViewController will never be instantiated and it will probably override init methods with fatalErrors for that purpose.

If BaseViewController is not a UIViewController, we might be breaking the semantics of inheritance here. BaseViewController is-not-a UIViewController. And we’re doing so just for the sake of reusing code.

Swift does not provide multiple inheritance so, at this point, you could think about solving this issue by moving from subclassing to conforming a BaseViewControllerProtocol with default implementations, but that might be even worse, as you can read here.

Let’s use Composition!

Let’s see how we could rewrite the first example using composition:

  • Here we are injecting Functionality1 and Functionality2 only in the classes where we need them.
  • We are depending only on the methods that we need or use.
  • We can change the implementation at runtime injecting any class conforming Functionality1Protocol/Functionality2Protocol.
  • Functionality1Protocol and Functionality2Protocol could be conformed by two classes as well as one single Class.
  • We encapsulate the dependencies.
  • We can inject mocks conforming Functionality1Protocol and Functionality2Protocol for Unit Testing.

Thank you for reading! If you liked the article, please clap :)

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Alberto Salas
Alberto Salas

Written by Alberto Salas

iOS Engineer. UX, Agile, Lean, and Software Architecture Hooligan. www.linkedin.com/in/albsala

Responses (1)

Write a response