Why I fell out of love with inheritance

Yonatan Korem
May 9 · 5 min read

Seven years ago, when I was near the end of my BSc, I was sure that inheritance is the greatest thing an OO programmer has in her arsenal of tools. During most of my studies, we were taught the principles of OOP, why interfaces are important, and how inheritance is a great tool for reusing code, abstracting your code and design, and what a generally powerful tool it is. That’s not incorrect. It is good for those things. But it took me a few years to realize that when I idolized those concepts, I tended to use inheritance where it shouldn’t have been used.

We were shown the example where inheritance fails:

A circle, and an ellipse. Which is the abstraction of which?

But we were also told examples like this one:

class employee {
pay()
generateHoursReport()
assignVehicle()
}
class Manager extends Employee {
assignEmployee()
}
class CEO extends Manager {

}

And when you think about it in terms of technical abstraction, it makes perfect sense. A CEO is a Manager. A Manager is an Employee. And a CEO is an Employee.

All three share the basic functionality of the employee, and each extends those functions, and/or add new functionality. This solution is exactly the solution I would come up with, before I actually started to internalize the first principle of the SOLID principles, and learned more about the delegation/composition pattern.


Single Responsibility Principle

Robert C. Martin wrote about the SOLID principles in the Agile Software Development and the definition was that

A module (class, interface, package,…) should do only one thing.

It was later refined by Martin to state

A module should have only one reason to change

I won’t go into details on the meaning of the refinement, and you’ll find a lot written about it already, especially in Martin’s own blog. But let’s apply this principle to our Employee example.

Looking at the Employee class, we can see that it handles financials, motor department, and HR subjects. It handles three things. To discuss it with Martin’s refinement in mind, there are three departments that might give a reason to change something in the employee class.

  1. Payroll department decides to update the way they calculate the salary of employees.
  2. Motor department wants to make it so only managers can be assigned vehicles due to budget cuts.
  3. HR replaced their reporting system and require a new template for the hourly report.

It took me a while to understand why this is even an issue. I would think that the Employee class knows how to handle the specifics for its own level, and once you travel up (down?) the chain, the Manager will probably override the things it needs, and so on.

  • You implement the pay (finance) and generateHoursReport (HR), and quickly notice a piece of repetitive code between the two that counts the total amount of hours. You’ve read about code smells and you don’t want to repeat yourself, so you refactor it out to a third, private method: getTotalHours.
    Months later, HR wants to change the report so that it shows the remaining hours. The new guy is not familiar with the system and changes getTotalHours so that it returns the value times -1. generateHoursReport now works as expected, but pay is now very broken. The change requested in one method affected the other.
  • We have this structure, and now we want to introduce a new type of employee: Contractor. The contractor needs to generate a hours report, but not to your system, and your system does not need to pay him because you pay to his company. You want to have Contractor extend Employee, but the new class should not include a pay method. Maybe have Employee extend the Contractor, but an employee is not a contractor.
    So you start to introduce base classes that do not actually represent anything, it’s just a “base” class to extend from.
    I promise you this: a year later, your simple inheritance structure became a huge inheritance tree, with classes like RootEmployee, ContractorWithVehicle, and TechnicalManager that cannot have any employees.

How can we change Employee and its structure to avoid these issues? How can we design the system to support all of these departments?

One way to do it, is to compartmentalize the logic to three different parts, and change the design to one of composition & delegation. This creates four pieces of code, each responsible for one topic: HR, Payroll, Motor, and the Employee that composites the three, and interfaces with them by providing information for the relevant employee. With this modification, our design now looks like this:

interface IPaymentStrategy {
pay()
}
class SalaryEmployeePayment implements IPaymentStrategy {
pay() {...}
}
interface IReportFactory {
generateHoursReport()
}
class HourlyEmployeeReportFactory implements IReportFactory {
generateHoursReport() {...}
}
interface IVehicleDecorator {
assignVehicle()
}
class LeasingVehicleDecorator {
assignVehicle() {...}
}
class Employee {
private IPaymentStrategy paymentStrategy
private IReportFactory reportFactory
private IVehicleDecorator vehicleDecorator
public Employee(IPaymentStrategy p, IReportFactory r, IVehicleDecorator v) {
...
}
pay() { this.paymentStrategy.pay() }
generateHoursReport() { this.reportFactory.generateHoursReport() }
assignVehicle() { this.vehicleDecorator.assignVehicle() }
}
var employee = new Employee(new SalaryEmployeePayment(), new HourlyEmployeeReportFacotry(), new LeasingVehicleDecorator())var manager = new Employee(new ManagerPayment(), new HourlyEmployeeReportFactory(), new LeasingVehicleDecorator())var ceo = new Employee(new ManagerPayment(), new HourlyEmployeeReportFactory(), new CompensationVehicleDecorator())

With this separation, we only need one Employee class, and the inheritance is provided by giving it different behaviors for each part.
Replacing the behavior of one element has no effect on the other parts of the system, which is basically the core idea behind the single responsibility principle.

Inheritance may still be used, but it will be applied to a single element of behavior, and will not cause the “ContractorWithVehicle” type of problem or create a need for multiple inheritance (a.k.a. Diamond Inheritance Problem).


Composition & Delegation

Composition does not necessarily mean delegation. The composition design pattern describes a way to define an object that is defined by its connection to other objects: TreeNode, or ListNode are the most basic of examples for it.

Delegation does not necessarily mean composition. Delegation (as is implied by the name) can be used when we want our object to have answer to a specific API, without being connected to the implementation of it.

The above example, and plenty of others like it (like GameObject) show how using inheritance can sometimes lead to poor design, especially when the inheritance is already implemented and more, different usages appear.

The composition design has more benefits:

  • It makes our design more testable, since each part of the code can be tested completely separately
  • It makes our design more aligned with the Liskov Substitution Principle (which I might write more about in the future)
  • It makes our design more aligned with the Interface Segregation Principle

Together, they make us look at our classes in terms of behaviors, and not as only “X is a Y”.


I want to stress one final thing about “loving” a concept. In the past, thinking that a specific design is “best” often got me to a place where I tried to fit the problem to the design, and not the other way around.

I do not wish for you, the reader, to close this browser tab and go right to your code and start to replace every inheritance with composition & delegation. It has its drawbacks and benefits, just like inheritance. What I would like for you to do after closing this page, is to take the time to review your code, and try to find instances of inheritance in your design that might be better with composition & delegation.

A good way to read code and notice places where it can be improved, are the various code smells, and their meaning.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade