Inheritance: just stop using it already!

no_inheritanceThere are certain constructs in programming languages that demonstrably lead to bad code in the vast majority of use-cases. There is a term for these constructs: anti patterns. An anti-pattern is a way of solving a problem that often appears quick and simple, but that leads to more work and more problems in the long run. Think of nailing two pieces of wood together, rather than drilling a guide hole and using a screw. The former is quicker, but the repair when the wood splits can be both costly and time consuming. Programming languages can enable, and sometimes even encourage, such anti-patterns. Whilst goto might be the best known, there are three other contenders for the worst anti-pattern of all: not writing automated tests, null and object-orientated (OO) inheritance. It’s the last that is the topic of this post.

Inheritance is one of the three fundamental features of OO. Entire languages and frameworks are built upon it. Therefore, any suggestion that it’s a contender for the worst software development mistake ever, had better be backed up some pretty compelling evidence. It turns out that there is actually a great deal of evidence.

Coupling
I’m going to take it as read here that everyone accepts that tight coupling is “a bad thing”. The ideas around why “loosely coupled, highly cohesive” systems are a “good thing” have been around at least since 1974, when Wayne Stevens, Glenford James Myers and Larry Constantine wrote their “Structured design” paper.

If class Bar is dependent on Foo, then they are coupled. How tightly they are coupled depends on the number of “coupling points” between then. Let’s say that Foo has two public methods, then Bar can become coupled to it via those two points. However, if Bar extends Foo, we open up all the protected coupling points too. Foo may even be designed to be extended and may expose aspects of its internal state via protected members. The very act of trying to make life easier for someone extending Foo necessitates more coupling points and so ensures that Bar becomes more tightly coupled to it.

Inheritance’s coupling problem doesn’t end there though. Extending a non-abstract base class via a child class can result in the Fragile Base Class Problem. This problem can be highlighted with a simple example. Taking three simple classes: Animal, Mammal and Bird:

Having created that simple hierarchy, we then realise we need to handle bats, which are mammals but also have wings. However, if we add support for wings to Animal, we risk breaking functionality in Bird. In this case, things are simple: all three classes are gathered together and can be reworked. But what if Animal had been a public API class for some library used by third parties? It becomes risky to make any changes (even just extensions that appear to meet the Open/Closed principle) to base classes.

Inheritance creates a highly unusual (if not unique) coupling problem therefore: the child class easily becomes tightly coupled to the base class and the base class is tightly coupled to child classes through the fragile base class problem. It’s difficult to think of a tighter coupling than bi-directional dependencies between a base class and all of its children!

The first of these problems can be overcome by ensuring that Foo is as abstract as the language will allow with no protected members. For many languages that means ensuring that Foo is an interface. The second problem can be addressed by ensuring those interfaces comply with the single responsibility principle and are as small and focused as possible. Thus, in the animal example above, we would likely use a mixture of interfaces and composition to solve the problem as would have Mammal implement the WingedAnimal interface when a need to support bats was identified.

Testing
When it comes to writing unit tests, we want the code to be as “test friendly” as possible. Any obstacles to testing will likely result in more complex, more brittle tests. Complex tests are hard to maintain, and brittle tests (ones that break for reasons other than a change broke a feature) are also hard to maintain as well as lowering confidence in the worth of those tests.

There are two things that together cause the most problems when it comes to writing tests: state that’s not under the complete control of the test, and coupling. Perhaps unsurprisingly, inheritance inherently results in both of these problems.

As already covered in the previous section, inheritance creates two way coupling between the base and child classes. Non-abstract base classes will – just like any other class with implementation details – also create coupling in test cases. Assume Foo has a set of properties, X, Y and Z and we have a method F() that takes aninstance of Foo and returns the sum of those three properties. We have a choice, we can create an instance of Foo and supply it to F() or we can try and override the behaviour of Foo.

If we use Foo directly, the test becomes dependent on the internal behaviour of that class. Change the way that F() internally manipulates X, Y and Z and the test may break. It won’t break because the assertion is now wrong; instead it breaks because the setup fails. We have a brittle test.

If we create a test subclass of Foo, we may then run into another problem. We potentially need to completely override all public and protected members of Foo, which can result in lots of extra code that must be maintained as Foo changes. Then we have to deal with Foo‘s constructor. If that constructor is well behaved and only sets local fields from its parameters, we’ll be OK. If it starts calling other methods, creating side effects etc, then the problems just get more and more difficult to work around.

Yet again, the solution to these testing problems is simple: make Foo either a completely abstract class or an interface. Creating a test implementation of an interface trivial and leads to simpler, far more robust tests.

Encapsulation
Along with polymorphism (more on that later), encapsulation is another of the three core principles of OO. It seems reasonable to therefore assume these three principles should be complimentary. It’s hugely ironic therefore that designing a class to support inheritance directly weakens that class’ ability to encapsulate its implementation details!

A class designed to be inherited from, might for example, have code like:

The class appears at first glance to be a true “gentleman” class that positively encourages child classes to tap into DoSomething in order to affect behaviour. However, there is a very important rule to classes: the public member signatures are the API; everything else is implementation details. The public API needs to comply with the Open/Closed principle. The implementation details are private though and should be free to change at any time, just so long as they do not change the public API. In other words, the internal workings should be encapsulated. That apparently helpful line, DoSomethingInChildClass() turns the internal workings of the class into part of its API contract: it creates a hole in the inner workings of the class, coupling the base class to all future child implementations and ruining its encapsulation in the process.

With OO, you make a choice: encapsulation or inheritance. Yet again, interfaces come to the rescue by allowing both to co-exist. Interfaces just describe the public API contract that a class must meet; it contains no implementation details and so cannot either expose internal workings itself or lead to other classes needing to do so.

Multiple inheritance
If you have read this far and are still somehow hanging on to the idea that there might be some good to inheritance, this section’s title may have raised your hopes. “Oh, I know about the diamond problem, but my chosen language doesn’t allow multiple inheritance; it uses interfaces”. A couple of things. Firstly, if you favour Java, guess what, Oracle went and messed with your interfaces by allowing them to contain implementation details: Java 8 supports multiple inheritance. Secondly, single inheritance causes no end of design problems too.

Inheritance is damned with multiple inheritance and damned without it. Unsurprisingly, a nice mixture of interfaces and composition of those interfaces yet again solves both. The multiple inheritance problem goes away as the diamond problem simply doesn’t exist with interfaces (ignoring Java 8’s default methods in interfaces, of course). The single inheritance problem, such as how to model a member of staff, who’s an admin of some features, a user of others as well as a gold customer for some policies and a bronze one for others, is a trivial design problem when composition is used, despite being nigh-on-impossible with single inheritance.

Polymorphism
Surely, polymorphism offers a last-gasp chance for inheritance? Thankfully, no. Whilst polymorphism-via-inheritance might seem to offer a panacea for avoiding the inevitable cyclomatic complexity of switch statements, subtype polymorphism (which is the type of polymorphism that inheritance supports) isn’t the only polymorphism available in many OO languages. Both Java and C#, for example, offer method overloading (ad hoc polymorphism) and generics (parametric polymorphism). And both still support the switch statement.

Polymorphism is a viable construct without inheritance, and we haven’t even touched on how much more powerful pattern matching is over polymorphism.

Conclusion
Inheritance really is a train-wreck of a programming feature. From creating some of the worst coupling problems that can be found in any code, through causing numerous problems when writing unit tests, to destroying encapsulation, inheritance throws endless sources of trouble in your path and only offers the illusion of making things simple in return. All of these problems are genuinely easily solved by using interfaces and composition. Ergo, don’t just “favour composition over inheritance”, instead just don’t ever use inheritance. Ever[1].

[1] Caveat time: there are actually one genuine occasion when inheritance has to be used. That is when using a framework or library that forces the adoption of inheritance to use a feature. For example, in C#, all exceptions must inherit from the Exception class. This isn’t optional, so we have no choice but to use it in those cases.

3 thoughts on “Inheritance: just stop using it already!

  1. How would you, to continue with the example, design the .NET framework to not use inheritance with the Exception ?

    You do make a good argument but to completely let go of inheritance sounds a tad bit dogmatic.

  2. @Éric,

    Apologies for the slow reply. To address your points:

    How would you, to continue with the example, design the .NET framework to not use inheritance with the Exception ?

    That’s an easy one. I’d have created a IException interface, which all exceptions then implement. I’d also have a Context property that the CLR’s throw mechanism would then populate with details like the stacktrace.

    You do make a good argument but to completely let go of inheritance sounds a tad bit dogmatic.

    This response really frustrates me. I have presented lots of evidence as to why inheritance is flawed and have presented perfectly reasonable alternatives that aren’t. Concluding that inheritance shouldn’t be used is obvious from this. I would argue that those that respond with “inheritance still has its uses” are the ones really being dogmatic, as they refuse to let go of a discredited idea.

Comments are closed.