Swift: Favor Composition Over Inheritance. The BaseViewController case.

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 anyBaseViewController
subclass is allowed to access members inherited from it, changes inBaseViewController
may require maintenance of the subclasses as well.BaseViewController
↔︎ Subclasses (II): If anyBaseViewController
subclass is allowed to override members inherited from it (as long as they are non-final), overrides ofBaseViewController
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
andFunctionality2
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
andFunctionality2Protocol
could be conformed by two classes as well as one single Class.- We encapsulate the dependencies.
- We can inject mocks conforming
Functionality1Protocol
andFunctionality2Protocol
for Unit Testing.
Thank you for reading! If you liked the article, please clap :)