Avoid Inheritance for Code Reuse

Image credits: Photo by Myburgh Roux from Pexels:

Most popular programming languages offer inheritance as a way of sharing code between classes. I frequently see developers using it eagerly, without giving it a second thought. Often, there are much better ways to structure a program and avoid code duplication. In fact, using inheritance excessively may result in a maintenance nightmare.

The Problem

Many object-oriented languages like Java, Python, C# or C++ provide classes as an essential way to structure programs into composable units. This is fine, but things get complicated when we add inheritance. Typically, a class can be extended by another class to inherit signatures and implementation details from its parent. Therefore, instances of a child class have all the fields and methods of their parent class. Finally, we can add additional fields or methods, call existing ones or change them by overriding.

Sounds simple, right? Yes, in a small text book example. But, in real-world programs, it can quickly become a maintenance problem.

Firstly, it is quite hard to reason about a program’s behavior when many levels of inheritance are used. Often, it is not clear where implementations are coming from, which sections are actually used by child classes, and which have been modified by overriding. Having to step through code with a debugger is quite common to understand program flow that is separated between several classes in a hierarchy.

Secondly, writing unit tests for code that relies on inheritance is often problematic due to tight coupling. Most of the time, it is not possible to isolate a class from its parents. Hence, we are forced to include the parent classes in our tests, too. This can cause problems due to unwanted dependencies and preconditions. Mocks or stubs are typically not a solution in this case.

Thirdly, there are frequent mismatches between what a base class offers and what a developer actually needs for reuse. This is because the original design could not foresee all the eventualities of the future. Typically, we want to reuse some sections from a parent class but avoid all the rest. As a result, we have to deal with a lot of unwanted code. We can modify the base classes instead, but avoiding the breakage of other classes is often difficult.

The Solution

The solution is quite simple: Avoid using inheritance by default and instead prefer composition for reusing code.

If a component A needs a component B, then we can simply provide B as a delegate to A. This can be a field, a parameter or even a function that we pass around. Composition is less prone to the problems mentioned above and therefore better suited for long-term maintenance. Of course, inheritance still has some valid use cases, but we should have a good reason for choosing it beyond just code reuse.

Now, what is your experience with inheritance vs. composition? Have you ever experienced problems that came from too much inheritance?

#SoftwareEngineering #Inheritance #Composition #Maintainability

Bastian Isensee
Bastian Isensee
Software Engineer (Freelancer)

Quality-driven Software Engineer focused on business needs, knowledge sharing, FinTechs, Golang and Java