Avoid The Class Hierarchy Jungle: Favor Composition Over Inheritance
September 12, 2017
Have you ever worked on an application with a jungle-like class inheritance hierarchy? Everything in the app inherits from two layers of classes and it’s impossible to follow a single line of functionality. You get code reuse, but at the cost of incomprehensible spaghetti code. I, for one, find that price too steep. In this post, we’re going to learn how to build code that’s easy to reuse, easy to test, and most important, easy to read.
Composition > Inheritance
We have many design patterns in object oriented programming. One the most useful ones is the composite reuse principle (aka. composition over inheritance). This term sounds a little opaque, but it's not hard to understand. It means that you should design your classes as a series of loosely coupled components instead of using a multilayered class hierarchy for code reuse.
Here’s an example:
Practically speaking, this means breaking down inheritance hierarchies into plug-able services. Then you can inject those services using your favorite dependency injection framework.
While using composition may seem more complicated, there are several advantages.
- It's far easier to reason about the code. If you divide up your functionality into small components, each component is simple. You're dividing up the complexity of the application into manageable chunks. When you're using complex inheritance, it's difficult to figure out what block of code is executing. This is especially true once you start selectively overriding methods.
- It's much easier to reuse a single component than to glue a class onto a hierarchy.
- It's easy to unit test loosely coupled components. Building the appropriate mocks to test a complex class is painful. Mocking a few interfaces is much easier.
Spotting Refactoring Opportunities
There are a few potential anti patterns to keep an eye out for.
"Base<thing>" classes. Especially base controllers (MVC), base pages (on Web Forms), and other base classes for classes that process data. Base classes for data storage objects are usually OK. ( ex: An Administrator that inherits from a Person class.) Using inheritance for processes is a bad idea.
More than two layers of inheritance. It's hard to imagine anything that needs more than two layers of inheritance.
Ginormous "God" classes that span 1000's of lines. While not strictly related to using composition over inheritance, this goes against the idea of building a suite of simple components. Large classes are difficult to read and to test. Flattening a class hierarchy into a "God" class is not an improvement.
Base classes with only one class that inherits from them. The base class here is superfluous. Feel free to get rid of it.
If you're using class hierarchy for code reuse, ditch that approach and favor composition instead. Your code base will thank you for it.