I received an email from one of my students asking for a design advice. He is working on an application used by a sports team manager, where they can create, edit and delete players, teams and coaches. So, the design that my student had in mind is something like this UML diagram:

So, a SoccerPlayer is a Player, which in turn is a Person. This is what many text books on object-orientation programming teach you:

When you can express the association between two concepts using IS-A, that’s an inheritance!

Many of these books claim that by creating these abstractions, in this example Person, you can “re-use” them in other projects. However, in 15 years of my professional experience, I have hardly seen many (or any) examples of such abstractions being used across projects in a useful manner.

At one point, I started creating a class library of these abstractions that I could share across projects. After a little while, that class library ended up being bloated with lots of unrelated abstractions and versioning it across different projects ended up being a nightmare. If you have done this before, you may relate to this story.

An extreme example of inheritance

Once I was engaged as a consultant for a greenfield project, and in the introductory session, the data architect of the project walked me through tons of UML class diagrams (more than 50 pages). Every class was inheriting from another class and eventually, they all led to a class that was called “Thing”! No joke!

Interestingly, there was not even one behavioural diagram expressing the behaviour of the system. So this huge inheritance hierarchy was only for inheriting “attributes”, not behaviour, which is what inheritance is all about!

Guess what? After spending more than a year and half a million dollars on that project, the company decided to shutdown the project. Nothing was produced, not even a beta version! They engaged various consultants and teams and no one could implement an application with such a large complex model.

Does this story sound familiar to you?

Don’t model the universe!

When building software, you should design a domain model for an application, based on the needs of that application and not the reality. The use cases that the application is going to fulfil will determine the angle at which you should look at the domain model.

In the real world, a SoccerPlayer is a Player and a Player is a Person. But just because you can express such relationship in English, doesn’t mean you should add such association between your classes. You’ll end up polluting your model with lots of unnecessary complexity that serves no purpose.

Inheritance increases coupling in your design

So what is wrong with inheritance? Well, before explaining, let me clarify something. I’m not anti-inheritance! Inheritance, just like every thing else, has its uses. When you use it in the right context, it works for you. But if you abuse it, it leads to increased complexity in your applications.

Inheritance creates tight coupling between your classes. If class Child derives from Parent, it is tightly coupled to the Parent. If you make any changes to the Parent, you may have to modify the Child as well. Or at a minimum, you need to re-compile it and re-deploy the assembly in which it is defined.

Now, when you have a small hierarchy with a few classes, that is not an issue. But as your hierarchy grows, the impact of change grows in your application. The further on top of the hierarchy you make changes, the more classes may be affected or at least need to be re-compiled.

When to use inheritance

So, when should you use inheritance? When re-using behaviour and potentially overriding it, which leads to polymorphism. But even then, you could use composition to achieve the same thing with less coupling in your design. The famous saying favour composition over inheritance explains that. I’ll write a separate post on this topic soon.

Does this sound too abstract for you? Ok, continue reading for some simple pragmatic tips.

When not to use inheritance

I love theory in practice. If you’re the same, let me give you my super simple and pragmatic advice by showing you some code. If you have one or more of the following symptoms in your code, you probably don’t need inheritance. You can collapse your hierarchy, reduce the coupling and simplify your design.

When you have hollow classes

Do you have classes like this in your design?

public class Person { public int Id { get; set; } public string Name { get; set; } } public class Player : Person { }

In case you’re not familiar with the C# syntax, here we have a Person class with two attributes: Id and Name. The Player class derives from Person.

The Player class here is what I call a hollow class. It serves no purpose. We could simply use the Person and reduce complexity in our design.

Here is a variation of this example:

public class Person { } public class Player : Person { public int Id { get; set; } public string Name { get; set; } }

In this example, Person is a hollow class and is useless.

Here is the important part I want you to pay attention to: in the domain of this application, what matters is the Id and Name of each Player. The fact that a Player is a Person in the real-world does not matter in this application. And that’s what I meant by not modelling your applications based on the reality. Instead, you should model based on what the application needs and how it is going to behave.

When your inheritance hierarchy is all about attributes

Look at this example:

public class Person { public int Id { get; set; } public string Name { get; set; } } public class Player : Person { public byte Number { get; set; } } public class Coach : Person { public byte YearsOfExperience { get; set; }

The argument behind this design is that we’re re-using the Id and Name properties in the Player and Coach classes. I used to have many classes like this before! The extreme example I mentioned earlier was exactly like this.

But re-using attributes is a poor way of thinking about inheritance. How much effort would be to duplicate these attributes in the derived classes? It would take only a second to copy/paste them. You may say but duplicating code is bad. Yes, but not always! It is bad if you’re going to change it and have to make the change in multiple places. But how often would these attributes “change”? Not often or never! How about the logic? Logic, algorithm and behaviour can change often.

That’s the reason that you should reserve inheritance for re-using behaviour, not attributes. But even then, you can use would better use composition.

Let’s sum it up

So, remember, inheritance increases coupling between your classes. Reserve it for situations when you want to implement polymorphism, not just for re-using code, especially attributes. If you have one or more of the following symptoms in your design, you probably don’t need inheritance:

Hollow classes that have no members

Base classes that include only attributes. They don’t have any methods for the derived classes to re-use or more importantly, override.

If you see this in your design, you can achieve a simpler, cleaner and more maintainable design by collapsing your inheritance hierarchy.

So, keep it simple!

If you enjoyed this post, please share it and leave your comments below.

Related

Tags: csharp