“Favor composition over inheritance”
You’ve probably heard this Object-Oriented Programming adage over and over again, but have you ever wondered what it implied? What does it mean in the context of iOS development?
Together, let’s dive into composition and look at it from different angles, including:
- Why composition is important?
- What does composition look like on iOS?
- When do we know to break something down?
- How do we know if we’ve gone too far?
Let’s get swifty!
The Case for Composition
Have you ever heard of the FizzBuzz test? If not, here it is:
“Write a program that prints the number from 1 to 100. But for multiples of three, print “Fizz” instead of the number, and for multiples of five, print “Buzz”. For number which are multiples of both, print “FizzBuzz”.”
Pretty straightforward, right? This type of problem is often used to teach programmers how to do Test Driven Development (TDD). They write a test (like “Print fizz when multiple of three”), then write the implementation, then write another test, and on and on until they have a running program.
But for many, it’s difficult to make the jump from “FizzBuzz” (or many other code katas) to applying these techniques in a “real” application.
That’s not surprising. The difficulty with TDD is not knowing what the steps are. It’s knowing how to break things up to make them testable, and how to arrange them again to build an actual application.
In other words, the challenge is building your app out of FizzBuzz-like things and composing them together.
That’s why composition is important. It enables you to build applications out of small pieces that are easy to reason about. Then you can arrange those pieces into bigger units that coordinate between them. What’s more, proper use of composition makes so many other programming-related tasks easier, like testing and maintenance.
Let’s have a look at how we can use composition in our applications.
Composing Model Objects
Composing model objects comes most naturally, since our data is often already structured in this way. For example, if we wanted to model a user and his favorite movies, you might do something like this:
In this example, we’re using an array of Movie
objects to represent something with even more meaningful: these’s are user’s favorite movies.
What would the alternative be? If we didn’t use composition, we’d probably end up with something like this:
Obviously this is unwieldy and unnatural for a data model. Could you image having a different boolean property for every user? That simply wouldn’t scale.
When it comes to our data model, there aren’t any frameworks holding us back. Composition emerges naturally as we model our domain. You don’t need to use any special API, it’s all up to you.
Let’s move onto something a bit more interesting.
Presentation Models
Now we want to display our favorite movies in a list. We want to show the title and a short synopsis. We could easily set this up if we have the following model:
Simple enough. Now, let’s imagine we want to display the first sentence of the synopsis on our iPhone layout, and the full synopsis on our iPad layout. How would we accomplish this? We could try to make a computed property for the short synopsis, like so:
This technically works, but I can see this getting out of hand. For example, what happens the day that you need to support tvOS and show the longSynopsis
? Do we add another property to our model? shortSynopsis
and longSynopsis
are not really properties of our model anyways; they represent a view into one of our model’s properties.
Instead of endlessly fattening our model with properties related to display, perhaps there’s a way to encapsulate this logic in its own struct and compose it with a Movie
.
This is a presentation model. It wraps a model object and handles its display.
(Some of you might call this a ViewModel
, but let’s not get into that right now.)
We can compose our Movie
object with this MoviePresenter
object to make sure that we always have the correct synopsis displayed.
As you can see, we can easily compose any movie with a MoviePresenter
struct to enhance it and make it presentable.
Composing UIViews
Another thing we commonly compose are views.
For example, if you’re building a validated form field out of UIKit components. You could code one up from scratch, but why not build on top of UITextField
for the input, UIImageView
for the validation status, and UILabel
for the error message?
Luckily, UIView
is built with composition in mind, and we have great API support for composing views together.
What’s more, since UIView
is specifically built to be subclassed, you have a convenient parent to serve as the root of your composition. It can handle the delegates of its children and coordinate to make something bigger than the sum of its parts.
(Want to learn more about building UIViews
? Check out this article)
Composing View Controllers
Finally, view controllers. Here’s an area where I think we all have room to improve. If you’ve dealt with Massive View Controllers in the past, you’ll know what I’m talking about.
Imagine you have a view controller that loads data from an API. You have a loading view, an empty view, an error view, and of course, the view for your content. How often have you implemented all of this functionality in the same view controller?
Turns out, this is the perfect example to show how view controller composition can really shine. You content controller should only care about its content. Your error view controller should only care about displaying an error. Your empty view controller should only care about… Yeah you get the picture. And all this should be orchestrated by a parent view controller.
Composing view controllers requires a bit more ceremony than views. I’ve written about this before, so I won’t belabor the point here. If you want to learn more, check out this article.
The important lesson to keep in mind when composing view controllers is to “black box” them as much as possible. I like my view controllers to be simple, with known dependencies, and communicate using messages through a delegate or the responder chain.
When to Use Composition?
Sometimes it can feel like choosing when to break a class down into smaller components is more of an art than a skill. Here are some guidelines I use in my own thought process.
First, I ask myself if this object makes sense on its own. If so, it’s probably a good candidate for composition. For example, the classes DatabaseFetcher
, DatabaseImporter
and DatabaseMigrator
are presumably legitimate classes. When combined, they can coordinate to create something greater, like a DatabaseUpdaterService
.
But imagine you’re the developer opening the DatabaseUpdaterService
. I think you’d be happy to see it’s internals split between these three responsibilities. Because each of these 3 has meaning on its own.
My second rule is, when in doubt, to err on the side of composition. If you make too many small classes and eventually decide you need to consolidate their code, that’s easy. When you have something big and you need to break it down into manageable chunks is not as straightforward.
How to Know You’ve Composed Too Much?
Too much composition has its downfalls too. When you take an object A
and break it down into B
and C
, the number of types in your application increases. Do this over and over again for a few years on a longstanding project and you can easily end up with hundreds, if not thousands of different types.
This makes the task of onboarding new developers arduous at best. Suddenly, they aren’t only faced with a DatabaseUpdaterService
, instead they also need to deal with a DatabaseFetcher
, a DatabaseImporter
and a DatabaseMigrator
and figure out how they all work with each other.
If you get to this point, breaking up your project into modules is probably the way to go. Modules are the ultimate abstraction layer, hiding all of the “internal by default” classes and structs. This can lighten the cognitive load when working on a new project. When you’re dealing with UI work, you can be pretty confident that all the stuff in the Database
and Networking
modules can be abstracted away in your mind.
The next telltale sign of over-composition is when you start to see too much method forwarding. Notice this line from the MoviePresenter
example above:
It’s clear to see that this isn’t really adding any value: it simply serves to get to the underlying data. In most cases, this is fine. However, if your classes or structs serve primarily to forward method calls down to their children, your abstraction might not be pulling its weight.
Maybe taking a different approach (like using the Strategy Pattern) would be better. Imagine if we replaced our MoviePresenter
with a SynopsisDisplayStrategy
instead. This is a more focused tool, but it works all the same.
Wrapping up
We saw why composition is important in our software design, and we saw four different ways of using composition in our applications. We also looked at the different signs of when composition is working for you, and when composition isn’t.
I’m convinced that becoming comfortable with composition in a foundational skill in software development. Honing it over time will no doubt help you in your career.