The right tool for the job

This post is about one of the Design Patterns, the Template Method. Except for the fact that, well, it is actually about somethign competely different.

Some time ago, in a galaxy far far away, the team I was with was building an internal framework. Our code was supposed to be an amalgamation of rainbows and unicorns that the rest of the company would use as a starting point to build highly customised projects. Yes, it didn’t end well.

But something today made reminisce of a particular episode in that era. Fair warning, some details have been changed to protect the innocent and or make my example a little more relevant.

We were building a video player. The requirements were quite simple: the UI should be easily customisable, and playback events should be trackable (i.e. play, pause, progress), and should be handled in a way that it should be easy to integrate with a second screen solution. Mind you, the company’s business domain was online video delivery, so as you could imagine, this was kind of a big deal.

So, off we went to build the thing, machete in hand and bug spray in pocket.

The key piece of the design was a “manager”, a block of logic that coordinated the playback status with the player’s UI state, while offering a default integration with a second screen solution (also developed in-house).

Now, and this is important, we needed to provide that integration with a second screen solution due to internal business reasons, but the design should be open enough so that other teams could provide their own integrations (i.e. AirPlay or Chromecast)

After some lively discussions we decided to provide what we believed complied with all the requirements.

So, this was, more or less, our manager, translated to Swift 3. Because those were still the Objective-C days.

The idea was simple. We provided the bulk of the business logic in our manager, and subclasses of our manager should be provided for different integrations with second screen solutions.

Basically, we designed an system modelled after the Template Method Pattern.

If a team wanted to integrate this video player with Chromecast, they would just need to subclass our manager, override the extension points we had provided, and go on their merry way. If another team wanted to integrate with AirPlay, they could do the same.

But, what would happen if a team wanted to integrate with AirPlay and Chromecast?

Well, they could subclass our manager with an integration with Chromecast, and then subclass their Chromecast integration with an AirPlay integration that could call super as part of each method that it overrides.

And that’s my issue with this pattern: it is not exactly what I would consider a very open design.

So, what would be the alternative? As usual, it depends.
To begin with, this pattern can be extremely useful sometimes, and provide very elegant solutions to some problems.

But, in this case, and I guess in any case where an open design is the main goal, it might be better to define an interface for each behaviour that we want to be customisable, and inject them.

For example, we could change the previous design to something like this:

The original design was not bad. This new design, however, has a couple advantages over the original design.

First, the manager and the different implementations of the second screens are more decoupled. The manager can work with multiple different implementation of the second screen, and those implementations of the second screen can collaborate with something other than the manager.

Second, these implementations of second screens are more cohesive, and therefore are going to be easier to test and maintain in the long term. And this part, in my experience, is extremely important. When classes do one thing and one thing only, and all the logic related to that thing is encapsulated in the same class, it is way easier to go back to it months after the code was written.

And third, and more important, now it is way easier to compose behaviours. For example (and I know this is a bad example, but it helps me illustrate my point), if we were asked to send play/stop events to both our InHouse solution and Chromecast and playback progress events only to Chromecast, we could reuse the implementations we already have, like this.

So, I guess, the whole point of this post is “favour composition over inheritance”.

Except, of course, when inheritance provides a better solution to the problem at hand.

So, on second thought, I guess the whole point of this post is that there are trade offs to every approach. A good engineer should try to choose the approach the solves the problem better, with the information she has at hand at that particular moment.

Leave a Reply

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