loading...
Cover image for Why composition is superior to inheritance as a way of sharing code

Why composition is superior to inheritance as a way of sharing code

ruidfigueiredo profile image Rui Figueiredo Updated on ・5 min read

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.

Posted on by:

ruidfigueiredo profile

Rui Figueiredo

@ruidfigueiredo

Currently working as a contractor, mostly on Node.js and Typescript, also React. Also have a background in academia, I have a PhD in CS and worked as a researcher in AI.

Discussion

pic
Editor guide
 

The article misses out polymorphism which is the key use case for inheritance. You can't use a class which is composed of another class in the same context as the second class.

It also overstates issues with testing and lack of clarity of child classes. It requires a little thought but it's easy enough to do constructor injection with class hierarchies.

A good example where inheritance is essential is in presentation: it's vital to be able to process a range of similar but different visual components with the same code. Such visual components typically share a lot of internal code so interface polymorphism is not a good fit.

Like many issues, inheritance and composition are both valid approaches in different context rather than one being 'better' than the other

 

Hi James,

The article starts by distinguishing implementation inheritance from interface inheritance. And specifically the use of implementation inheritance as a way to share code (this is where you have a base class and you derive from it to access its methods, no polymorphism is used here).

If you are talking about inheritance being essential in presentation specifically to take advantage of polymorphism them I agree, but that was not the point of the article.

 

Hi Rui

Thanks for your response. My apologies that I misunderstood your limitation of the point you were making. I think we can then agree that in cases where you need polymorphism together with sharing code, inheritance has it's uses, but where polymorphism is not relevant, composition is preferable.

 

Do you have an example where subtype polymorphism cannot be replaced by composition and interface polymorphism?

 

One example would be a requirement to enforce behavioural invariants (one of the aspects of the Liskov substitution principle aka the L in SOLID). The compiler can't verify that you followed some rule in the dev manual that says, "all classes implementing IDependency must implement method x() by calling _dep.x(), and only _dep.x()". But, if you express that rule through final/non-virtual methods on a base class instead, then it can, and as a bonus it's self-documenting.

Another example, from a maintainability point of view: imagine having 42 classes implementing IDependency and a requirement to extend that interface with a new set of methods. Would you rather touch one or two files, or all 44 of them, even if the changes are mostly trivial? Java 8 added inheritance of default implementations to interfaces to support that exact scenario.

Lastly, imagine you would like to inherit several levels of bases because your problem domain is hierarchically structured, you need to conclusively demonstrate correctness in some parts, and want to leverage the type system to do that. So you end up with something like InvoicePaid (detailed business rule level) inheriting from TaxAccountable (legally required, with external code audit) inheriting from Financial (security rules about event routing) inheriting from Event (shared technical infrastructure).

Interface inheritance can usually deal with the correctness issue, because while the relevant behaviour depends on the type, it's typically implemented external to it (unlike in the first example), but imagine what a leaf class implementation would look like if these were more than just marker interfaces. And they will be, and you'll have lots of leaves.

What this boils down to is: every language supporting OO implementation design supports some form of implementation inheritance and also some form of composition (AFAIK), and there are good reasons for that. Traditionally, inheritance has been oversold by OO teaching materials, and overused as a result, but avoiding it completely is overreacting.

 

Inheritance creates very strong coupling between base classes and derived classes. It's very difficult to break this coupling, i.e. you become somewhat locked in to your original conception of how your system is structured. In long lived systems, this inevitably changes.

In my experience, the very dire consequence of this is that as your system grows in size and complexity, base classes tend to accumulate many aspects that may or may not apply to all of its derived classes. It's extremely difficult to reason about systems with base classes that are over several hundred (thousand?) lines long.

Composition is a far more flexible approach that allows a more fine grained approach to decomposing the various elements of your system.

OOP looks great when you consider the simple examples that they teach in computer science 101, like cats being mammals that are in turn all animals, or a teacher and a student being specializations of a person. However, it is extremely difficult to scale to today's large size systems, mostly due to the strong coupling I mentioned.

 

Hi Rui,

Excellent and simple article about Inheritance vs Composition.

After I read the book Pragmatic Programmers and Clean Code I gradually changed the way I code to the point I don't use Inheritance unless forced by the Framework I am using.

Composition === Explicit Code

Inheritance === Implicit Code

Nowadays I am 100% in favour of being Explicit over being Implicit.

 

"automated copy & pasting", I'm going to start calling inheritance this at work and see what happens!

 

Would be much more convincing with examples of real classes instead of meaningless ones. IMHO both inheritance and composition are useful, it just depends on the problem. Classic examples of Shape, Triangle etc. are much clear with inheritance.

 

They might seem clearer but that's just if you think about inheritance as an IS-A relationship. For example it seems right to say "A Triangle is a Shape".

Even though it sounds right, you get very little from that if you look at inheritance in OOP for what it is. It's just "... declaration of
methods and variables in a subscope and it has nothing to do with ISA whatever, and the notion of ISA can be very confounding."

Not my words, they are Robert Martin's (known for the SOLID principles and the Agile methodology). He said them in this interview: hanselminutes.com/145/solid-princi...

Here's the transcript: s3.amazonaws.com/hanselminutes/han...

IS-A comes form AI's knowledge representation field, where there are algorithms that take advantage of that information to perform inferences (e.g. Semantic Nets).

In OOP there's no inference, so in the end a "Triangle" inheriting from a "Shape" class just means that the public and protected members of Shape are available in Triangle.

Inheritance is useful in some situations, but not when think about it using the "IS-A" perspective.

 

The forget about the IS-A origin, in the real world a triangle is a kind of shape as well as a financial agent is a kind of person or a drug order is a kind of order. When defining entities belonging to a hierarchy I find it more natural to use inheritance instead of composition.

 

Its because you are seeing inheritance as a way to share functionality, when the purpose is to share characteristics.

 

There is no one true purpose to a design principal, as things evolved a lot along the way.
Inheritance is used in a lot of languages , with a lot of specific features, all designed for different use cases (of course some of them overlap).
That being said the theory and practice of Inheritance vary, and it is often used to share functionality, because it's relatively ease and clean to use.

 

Obviously you can use inheritance the way you want. Is not restrictive at all other than a few rules that change from language to language.

But overall, that's the key feature of inheritance: share characteristics among a descendants in order to create a cohesive domain.

I think the problem here is the terms we use.

I would love to see a reference to back you up. programming is prone to opinionated discussion, so some background to the opinion you hold might be helpful.

If by characteristics you mean properties, then I disagree, as a characteristic can also be a function (or a capability).

Anyway, I might be wrong, but check these out:

www2.latech.edu/~box/ase/tp/Per%20...

en.wikipedia.org/wiki/Object-orien...

esug.org/data/Old/ibm/tutorial/CHA...

 

Awesome article. Finally all those code samples I've skimmed through that seemed to have a redundant Interface and an immediate implementation right next to it are all clear!

 

Nice article, thank you. I never fully understood the "composition > inheritance" concept, but now it clicked :)

 

Coding with Inheritance is like predicting future

 

Great article.
Thank you for writing it!

 

Dependency injection instead of inheritance? In the second code snippet I see a dependency injected and a method of this dependency exposed through the method Method().