This is why you need Composition over Inheritance

I was attempting to make some changes to some fairly old code in our codebase (something I probably wrote myself…) which hasn’t been touched on for a while.

Naturally, my first step is to understand what the code does, so I started by looking at the class where I need to make my changes.

composition over inheritance_1

Perhaps unsurprisingly, I couldn’t figure out how it works.. The class contains only a handful of override methods, but I have no idea how they fit together.

So I started digging deeper through several layers of abstract classes, each filling in parts of the puzzle, until I reached the base of this class hierarchy.

By this point, I’m staring at a control flow full of strategically placed gaps. Going back-and-forth along the class hierarchy several times, I ended up with a vague sense of how the various pieces of logic scattered across the hierarchy fit together.

composition over inheritance_2

What’s more, where we needed to deviate from the control flow dictated by the base class, we have had to reinvent a brand new control flow mid-hierarchy, making it even harder for me to understand what’s going on.

 

This is not where I want to be… I want to be able to reason about my code easily, and with confidence.

I suspect a great many of you have experienced similar pains in the past, but how do you go about applying Composition over Inheritance?

 

Wikipedia’s definition and example of Composition over Inheritance focuses only on domain modelling, and I’m generally not a fan of conclusions such as:

To favor composition over inheritance is a design principle that gives the design higher flexibility, giving business-domain classes and more stable business domain in the long term.

In other words, HAS-A can be better than an IS-A relationship.

What does this even mean?!? Are you able to back up these claims of “high flexibility” and “more stable business domain in the long term” with empirical evidence?

 

In my view, the real benefit of Composition over Inheritance is that it encourages better problem decomposition – if you don’t break up the problem into smaller pieces (that are easier to tackle) first you have nothing to compose with later on. Scott Wlaschin’s railway oriented programming approach is an excellent example of how to apply composition in a practical and elegant way.

image

 

In his keynote The Old New Old New Things at BuildStuff 14, Greg Young described problem decomposition as the biggest problem we have in the software industry, because we’re just so bad at it…

And the challenge of problem decomposition is not limited to code organization. Microservices are all the rage right now, and the move from monolithic architectures to microservices is another example of problem decomposition, albeit one that happens at a higher level.

 

So I’ll be taking knife to the aforementioned class hierarchy and replacing them with small, composable units, using a language that is a great fit for the job – F#!

Will you follow my lead?

 

Links

Railway oriented programming

Is your programming language unreasonable

Service architectures at scale, lessons from Google and Ebay

Martin Fowler on Microservices

Greg Young – The Old New Old New Things

1 Comment

  1. Pingback: Fasterflect vs HyperDescriptor vs FastMember vs Reflection | theburningmonk.com

Leave a Reply

Your email address will not be published.