Inheritance is one of the pillars of Object-oriented programming (OOP), the other two being polymorphism and encapsulation. Inheritance is therefore centre stage whenever OOP is discussed. There are several flavours of inheritance however. There is implementation inheritance and interface inheritance. It is the former that is the focus of this post.
Implementation inheritance is described as a way to achieve code reuse. This works by defining methods and variables in a class that is then inherited by other classes. Those other classes are said to be derived from the original class, and the original class is said to be their base class.
The derived classes have access to the methods and variables from the base class, and this is how code reuse is achieved.
The idea of sharing code this way dates back to 1967, specifically to the SIMULA language which is credited to have been responsible for the birth of object oriented programming.
This concept dates back almost 50 years, it must be a great idea because it is still being used today. But put that thought aside and consider this instead. How would the scale of the programs written at that time compare with what we do today? I wasn’t born at that time, but I’ve heard from people who lived it that 500 lines of code was a very large program by the standards of those days. Today, that is very little.
Also, artificial intelligence was doing well at that time (this was before the AI winter), and there was this notion of IS-A that was very popular. IS-A is a concept that came from the field of Knowledge Reasoning, namely Semantic Nets. In a semantic net there are nodes and arcs. A node represents an entity, and an arc represents a relationship between entities. One type of arc is named IS-A where the properties from the source node of the IS-A relationship are inherited by the target node. Sounds familiar?
This idea had to come from somewhere, and although it is very useful in the field of Knowledge Representation and Reasoning where you can perform inference based on it (e.g. a Cat is a mammal, all mammals have fur, so a cat has fur), it is not so useful in OOP. In fact, the best ever description of this concept when applied to OOP that I’ve heard was from Uncle Bob: “…inheritance is not ISA. Inheritance, if you look at it with a very jaded eye, inheritance is the declaration of methods and variables in a subscope and it has nothing to do with ISA…”
This idea of IS-A is still popular today when describing how to use inheritance. Just do a search for “object oriented programming inheritance” and select images.
So why is it that implementation inheritance is not a good tool for code reuse? Well, it is heavily based on ideas from another area that do not apply in OOP, and there are better alternatives. I’m speaking of course, of composition.
First, let’s look at how code reuse looks like through inheritance and then how it looks like through composition.
public class Base
{
public void SharedMethod(){
//do something
}
}
public class MyClass : Base
{
public void Method()
{
//SharedMethod is available here
}
}
The first obvious thing is that the code we want to reuse is only available to derived classes, also most modern languages only allow one base class per class.
This design is opaque. What I mean by this is that by looking at the derived class the only information you have about the shared method is its name. Frequently you need to traverse the hierarchy up to understand what is going on.
Moreover, if the derived class has more than a couple of lines of code it becomes hard to distinguish which methods are defined in the base class and which are defined in the derived class, because there’s nothing to tell them apart.
And finally, it is not a test friendly design. Imagine that the SharedMethod
makes a call to the database and you want to write unit tests for the MyClass
class. It’s a hassle to mock the result of a call to the SharedMethod
.
How will this look like using composition?
public interface IDependency
{
void SharedMethod();
}
public class Depedency : IDependency
{
public void SharedMethod()
{
//a shared method's implemetation
}
}
public class MyClass
{
private readonly IDependency _dependency;
public MyClass(IDependency dependency)
{
_dependency = dependency;
}
public void Method()
{
_dependency.SharedMethod();
}
}
Now any class can use the code that we want to reuse, they just need to reference the dependency that has the code we want to share.
There’s more information in MyClass
for us humans. It’s not just a call to the method we are reusing without any context. Replace IDependency
by IMailService
and shared method by SendEmail
and it becomes obvious what it does and where it is implemented.
You’ve probably noticed that I used an interface IDependency
instead of a concrete class. This is just another thing that composition enables that is not possible when sharing code through implementation inheritance. When you use implementation inheritance you tie your derived classes to the particular implementation in the base class.
If you want to write unit tests for MyClass
then it is very easy to use any isolation framework to be able to mock the calls to IDependency
and test the method of MyClass
in isolation.
I almost feel like calling implementation inheritance automated copy & pasting, which if you think about, in its purest form, that’s what it is.
How come it is so popular, even though it’s a less flexible way of sharing code? The way it is taught is very appealing, especially the IS-A concept seems to make a lot of sense at first. I think this is why it prevails as probably the first thing most developers think when they want to share code.
However, when you take a hard look at implementation inheritance as a tool to share code you can quickly find that it is inferior, and that is why probably there are so many sources saying that you should prefer composition over inheritance. This is another one, with a little bit of history.