What is Single Inheritance, and Why is it Bad?

In a previous post we talked about how there are two different notions of inheritance, Interface Inheritance and Implementation Inheritance. In this post we’ll be talking primarily about the latter. That means, we’ll be talking about using inheritance to share actual logic among classes.

Let’s say we defined two base classes. The first is DatabaseAccessor:

class DatabaseAccessor {
   protected:
   bool WriteToDatabase(string query) {
   // Implementation here
   }

   string ReadFromDatabase(string query) {
   // Implementation here
   }
}

which encapsulates our logic for reading from and writing to some database. The second is Logger:

class Logger {
   protected:
   void LogWarning(string message) {
   // Implementation here
   }

   void LogError(string message) {
   // Implementation here
   }
}

this encapsulates our logic for logging errors and warnings. Now, suppose also that we are using a language the supports multiple inheritance, such as C++. If we want to define a class that shares functionality with both of these classes, we can subclass both of them at once:

class SomeUsefulClass : private DatabaseAccessor, private Logger {
   // Some useful code goes here
}

So, we have a subclass named SomeUsefulClass that can use both the DatabaseAccessor and Logger functionality, great!

What if we wanted to do the same thing in a language like C# or Java? Well then we’re out of luck, in C# and Java you cannot inherit from more than one base class. This is called single inheritance. How do we achieve the same effect if we are using either of these languages? Well, one solution would be to chain our subclasses together. We would choose which of DatabaseAccessor and Logger is more fundamental and make it the subclass of the other. Suppose we decided that everything has to log, then the Logger class remains the same and DatabaseAccessor becomes:

class DatabaseAccessor : private Logger {
   protected:
   bool WriteToDatabase(string query) {
   // Implementation here
   }

   string ReadFromDatabase(string query) {
   // Implementation here
   }
}

So now we can subclass DatabaseAccessor and get the Logger functionality as well. Although, what if we really did just want the DatabaseAccessor logic, and we didn’t actually need the Logger implementation? Well tough luck. Now everything that inherits from DatabaseAccessor inherits from Logger as well.

This might not seem like much of a problem with something as trivial as Logger, but in big enterprise applications, it can spiral out of control. You end up in a situation where all the basic re-usable code you might need is locked inside a lengthy chain of subclasses. What if you only need something from the bottom ring of this chain? Unfortunately, you have to pick up all of it’s base classes as well. This makes our code unnecessarily complicated and harder to read and understand. If one of those unwanted base classes does all kinds of heavy weight initialisation on construction, then it will have performance implications as well.

One consolation that is often offered is that both languages allow multiple inheritance of interfaces. This doesn’t really help us though. Implementation inheritance and Interface Inheritance are two completely different things. We can’t convert one of DatabaseAccessor or Logger into an interface. The entire reason we want to inherit from them is to get their implementation!

We could also use composition instead of inheritance. In this case we would inherit from one of the classes, and keep a reference to the other. But, in that case, why even use single inheritance at all? Why not just let our classes keep references to a Logger and a DatabaseAccessor? The language designers have struck a bizarre compromise here, we can use inheritance, but only a little bit. If C# and Java are Object Oriented, then they should allow us to use the full features of object orientation, rather than just a flavour.

The good news is that the people behind C#, Microsoft, have realised the error in their ways. They have released two features that ameliorate the problem of single inheritance, Extension Methods and Default Implementations in Interfaces. More on these in future blog posts.

Leave a Reply

Your email address will not be published. Required fields are marked *