I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.
— Abraham Maslow
When creating software, it’s useful to understand a wide range of design principles. Understanding how to design a system with the most appropriate principle can save countless hours of development and headache.
The first principle I’d like to talk about is Interfaces vs Inheritance, also known as Composition Over Inheritance:
What is inheritance?
Inheritance is when a class inherits state and/or behavior from a parent class. Here’s an example in Swift:
In this example, the GermanShephard
class inherits from the Dog
class. Because of this, GermanShephard
gains all of the properties and methods from Dog
, and can call bark()
.
Inheritance is extremely useful for modeling hierarchies like this one. Still, inheritance is not without its limitations. Take this example:
In this example, we have two breeds of dog that are able to sniff drugs: the German Shephard and the Belgian Malinois. Unfortunately, they both provide implementations for sniffDrugs()
, so there’s some code duplication here.
At first glance, it looks like you can reduce the code duplication by moving sniffDrugs()
to the parent Dog
class. Unfortunately, that would mean the Poodle
class inherits sniffDrugs()
as well, which doesn’t make sense, as poodles aren’t drug-sniffing dogs (at least for this example).
Another solution might be to create a level directly below Dog
that splits the dogs into two families: drug-sniffing dogs and non-drug-sniffing dogs:
This fixes the code duplication, but adds a bit of complexity to our model.
Now let’s say you need to represent dogs that can and can’t swim. For the sake of this example, let’s also say that German Shephards can’t swim (despite what this adorable video shows).
To represent this in our system, we may create something like this:
Did you notice the same code duplication we saw above? That’s because both the Belgian Malinois and the Poodle can swim.
Unfortunately, we can’t solve this in the same way as above, because BelgianMalinois
can’t inherit from both DrugSniffingDog
and a new SwimmingDog
parent class.
We can, however, model this with protocols in Swift.
How can protocols provide a better abstraction?
A protocol in Swift defines methods or properties that a class can then adopt. Here’s an example:
The GermanShephard
and BelgianMalinois
class both adopt the Barkable
protocol, meaning they must both provide implementations for bark()
.
As of Swift 2.0, we can now remove the code duplication by providing a default implementation for bark()
using a protocol extension:
This gives both the GermanShephard
and BelgianMalinois
the ability to bark()
. If we want the GermanShephard
to bark differently from the BelgianMalinois
, we would just need to override the bark()
method in GermanShephard
.
And, because classes can adopt multiple protocols, we can model the above barking, drug-sniffing, swimming model like so:
Now we can easily represent dogs with different abilities without getting stuck in a rigid hierarchy. Not only that, but you can reuse the Swimmer
(and maybe DrugSniffer
?) protocols for other classes, like Human
.
Conclusion
When designing a system, it’s important to pick the right design principle for your model. In many circumstances, it makes sense to prefer composition over inheritance.
Further Reading
- Mixins and Traits in Swift 2.0 by Matthijs Hollemans
- Protocol Basics by Austin Zheng
- Protocol-Oriented Programming in Swift (WWDC Video) by Dave Abrahams, Professor of Blowing-Your-Mind
- Analyzing Swift Protocol Extensions and C# Abstract Classes by Andrew Bancroft