OO Design Patterns: Composition Vs.
InheritanceLucas PenzeyMoogBlockedUnblockFollowFollowingJun 7As seems to be the case with every apprentice’s technical blog, it’s now my turn to write-up a post comparing and contrasting two staples of Object Oriented software design: composition and inheritance.
The first language I worked in for any meaningful amount of time was Ruby, which is of course Object Oriented.
Even before I really understood inheritance or composition, I heard the phrase “prefer composition over inheritance” numerous times.
Over time this morphed into “avoid inheritance” in my mental schema regarding OO languages, which I see echoed in numerous technical blogs across the internet.
However, I think that inheritance sometimes gets an unfair shake.
As with everything in software engineering, making a decision involves a careful weighing of tradeoffs.
Sandi Metz, the guru of OO design patterns and all around wellspring of programming wisdom, explains it succinctly:“Every decision you make introduces two costs — one to implement it and another to change it when you discover that you were wrong” — Practical Object Oriented Design in Ruby (POODR), Sandi MetzNotice how she says “when” you are wrong, not “if” you are wrong.
This is a good philosophy to keep in the back of your mind when deciding which path to take for any given decision, but it relates directly to why preferring composition over inheritance became a popular turn of phrase.
But before we get into which pattern is preferable, we need to first understand why we would be interested in implementing either of these two patterns in the first place.
If we follow the accepted dogma that a class/file/module/whatever should have a single responsibility, meaning it has one and one reason only to change, then we’re going to end up with a program consisting of numerous classes that need to collaborate in order to achieve their intended function.
Collaboration in an OO program necessitates the sending and receiving of messages between the various objects.
Inheritance and composition are both techniques for effectively managing the relationships between these objects and the way that they delegate messages.
Let’s first dive into inheritance, and the benefits and drawbacks that it provides, before getting to composition, and if we really should be preferring it over inheritance.
Note: there are multiple forms of inheritance, but this post will only be discussing single inheritance, where a subclass can inherit behavior from just a single parent class.
There are numerous other types of inheritance such as multiple inheritance, hierarchical inheritance etc that all have their own benefits and complications worthy of a separate discussion.
Also, do yourself a favor and go read Metz’s POODR Chapter 6: Acquiring Behavior Through Inheritance and Chapter 8: Combining Objects with Composition for an excellent breakdown of the pros/cons of both patterns.
Then do yourself another favor and read the whole book.
InheritanceIn terms of relationships between classes, an element that inherits from a parent element is said to have an “is a” relationship.
For example, it makes sense for square to inherit from shape, since a square is a shape.
Inheritance can be a helpful pattern when you notice that you’re defining the same behavior across multiple instances.
Back in the example of a program that deals with shapes of various types, you might have a method drawBorder that’s being duplicated across multiple different types of shapes.
So what’s the big deal you may ask?.The headaches arise when you want to make a change to drawBorder, or even worse when you discover a bug in the method.
Refactoring means you have to crawl around your project and update every single instance of the method.
A buggy drawBorder method means you’ve now littered your codebase with the same bug, creating widespread vulnerabilities.
Inheritance aims to solve this problem by abstracting the commonly used method into a parent class that provides a generic enough implementation of drawBorder such that it could be used to draw the border of any shape regardless of type (this “genericness” is very important, as having an overly specialized base can lead to a whole host of downstream issues).
Now, every new class that “is a” shape can inherit from the parent shape class and use its definition of drawBorder.
One of the main benefits of this is efficient code reuse, and automatic delegation of messages between classes.
We also get additional benefits of adhering to the open-closed principle, which states that components should be open to extension but closed for modification.
Say we wanted to add more types of shapes to our program that have their own unique characteristics.
With inheritance we can use the existing drawBorder method and extend it to new functionality without going into the parent class and modifying any of the code.
While open-closed is the goal, you’ll of course sometimes have to modify the parent class — maybe you discovered a bug, or a new way to improve performance.
Before implementing inheritance you would have to hunt down every instance of drawBorder, but now if we want to make a change all we’d have to do is modify the parent class’ implementation, and the change would cascade through every subclass.
Pretty sweet!The downside of this technique is that changing the parent class’ implementation will create cascading effects through every subclass.
Wait — isn’t that a benefit?.We’ve now arrived at the double-edged sword that is inheritance, in that with great power comes great responsibility.
Inheritance makes it trivial to change the behavior of a large section of your codebase with small changes in one location, which means that inheritance has introduced tightly-coupled dependencies into our program.
Every subclass directly relies on its parent, and changes to one might necessitate changes in the other.
An additional drawback of inheritance is that you’re usually making pretty broad assumptions about how the program is going to be used in the future.
Maybe you as the programmer can’t imagine a world where your drawBorder method would ever be given a shape that it doesn’t recognize, but you also never thought that the brain-bending Mobius strip will try to inherit from shape, and your program falls to pieces.
This is why it’s common to hear the refrain of preferring composition over inheritance.
It’s not that inheritance isn’t useful, but when implemented carelessly or as a solution to the wrong problem it can introduce significant headaches down the road, potentially to the point where your program is so tightly-coupled that adding new functionality without breaking existing features is all but impossible.
CompositionEnter our “preferable” friend composition.
Where inheritance refers to an “is-a” relationship, composition defines a “has-a” relationship.
A project that uses composition for orchestrating the interaction between components focuses on combining multiple small units that join forces to create objects that are more than the sum of their parts.
If we lay composition over our shape metaphor from before, we would try and think of the discrete elements that are being combined to create a shape.
We could end up with objects such as straightLine, curvedLine etc.
that we could combine and rearrange in different ways to produce the different shapes we want to use.
Then we could create a class that implements drawBorder with an interface that accepts an object composed of lines, and as long as it has lines, drawBorder would be able to successfully carry out its duties.
Separating out the responsibilities like this allows for greater independence of the individual parts and thus greater flexibility.
Unlike inheritance, composition doesn’t offer automatic message delegation.
We have to explicitly design each unit’s public interface so that it knows how to correctly interact with other parts of the program.
There could potentially be duplication in these interfaces, but with composition there’s no built-in way to reuse that code like we can do with inheritance.
The other side of this coin is that each unit is immune from potential side-effects of other components since they’re not directly inheriting behavior.
An additional consideration with composition is that while each unit is straightforward and (ideally) very easy to understand, the combination of all the parts can result in a complex and hard to understand whole.
Should We Really Prefer Composition Over Inheritance?In short?.Yes — but not in the way I used to think.
In general composition is the one you want to reach for if you don’t have a strong reason to use inheritance.
Inheritance, when used for the wrong reason or implemented carelessly, can lead to significant challenges for future you, or any developer that you’ll be handing the project off to in the future.
Composition introduces fewer dependencies than inheritance, and makes less assumptions about about how the program will be used in the future.
That being said inheritance offers powerful features that can provide excellent benefits when used correctly.
Maybe it would more instructive to rephrase “prefer composition” to “are you sure you need inheritance?”.
Choose wisely!Further reading/resources drawn on for this post:Practical Object Oriented Design in Ruby — Sandi MetzHead First Design Patterns — Kathy Sierra & Kathy Bateshttps://www.
com/insights/blog/composition-vs-inheritance-how-choose.. More details