Code smell of abstract classes

Introduction

If your software project has a lot of abstract classes, this may indicate that there is something wrong with it. Many abstract classes may indicate the following things:

  • Premature generalization
  • Complex inheritance tree

Of course, abstract classes have their use. However, your project having many abstract classes may be a sign of these problems.

Premature generalisation

When making some new software, a programmer may want the software to be easily adaptable, so that maintaining it and adding new features to it becomes easy. Sometimes, this is tried through premature generalization, which is a bad thing.

Premature or speculative generalization is taking into account all kind of features which are not currently asked for.

Dave Smith:

This is really going to be a clean framework. I'll make an abstract class out of this part so that folks can subclass it later, and I'll put in a bunch of well-commented overridable hooks in the concrete subclasses so that folks can use them as templates, and just in case somebody ever needs to build special debug subclasses, I'll put in extra stubs over there (somebody will thank me for 'em one of these days). Yeah, this is really going to be neat.

Ryan Farley:

Sometimes referred to as “over-abstraction”. Your requirements might be to code X, but you think it would be really cool to also account for the possibility of YZ so you abstract, or overly generalize your code to include for the future possible need for XYZ. You brag to your fellow programmers or to your client about how not only you can do X, but also if you ever need to do Y or Z then that is already built in. “How cool is that?!”. Well, it might be cool, but chances are that Y and Z will never be needed.

SourceMaking.com:

You get it when people say, “Oh, I think we need the ability to this kind of thing someday” and thus want all sorts of hooks and special cases to handle things that aren’t required. The result often is harder to understand and maintain.

There you have one of the problems with premature generalisation: the code becomes harder to read and maintain because it has things for features you are not going to need.

Eric Gunnerson:

The problem is that there are at least three distinct layers in the layout manager. I write a line of code that says:
toolbarTable.SetColMargin(0, 10);
and when I step into it, I don't step into the appropriate TableFrame. I step into a wrapper class, which forwards the call onto another class, which forward onto another class, which finally does something.

Of course there will be features that get added later on. The problem is, you can not foresee which features they will be. If you take into account all kinds of features that may be added later on, somebody will think of a feature that you had not taken into account. The generalised structure of your code does not help with this feature, so you did it all for nothing.

Johan Karlsson:

After a couple of weeks it was decided that we was going to use another low level protocol for the communication. It turned out that my general design was useless, since I did not anticipate all the aspect of the problem. First of all I realized that I do not have the powers, like Nostradamus, to see into the future. I also came to the conclusion that you have to know the domain, before making any generalization. You have to do your first implementation in that domain, before you should make any general design. When you continue to evolve your product, you can figure out what parts of the code that is reasonable to generalize.

Dave Smith:

Over time, I've learned that trying to get an abstraction right the first time is like premature optimization--until you can make decisions based on real usage patterns, your early guesses are liable to be off.

Complex inheritance tree

(also known as the "Terrible inheritance tree of doom", "Drunk Deriving" or "Idiotree")

If you have a inheritance tree of more than three levels deep, you have a problem. First of all, you should be careful with any inheritance, because it couples class tighter than need be:

Prof. Dr. Dominik Gruntz

Inheritance breaks encapsulation. Support for inheritance implies that (some) implementation details have to be published!

publiclook on Abstract Classes:

The WORST examples of inheritance are things like subclassing Button every time a Button that communicates with another object is needed. Many (all) of the famous C++ design patterns exist to reduce coupling between objects. Events, the Command pattern, and The Chain of Responsibility pattern exist in part to avoid having to subclass Button ;) C++ templates also exist to avoid subclassing every time a new type needs to be used. Isn't it interesting how many idioms, patterns, and language constructs exist to avoid subclassing ?

Furthermore, there is a perfectly good alternative, especially if you are developing an abstract class:

Paul John Rajlich

In general, object composition should be favored over inheritance. It promotes smaller, more focused classes and smaller inheritance hierarchies. Most designers overuse inheritance, resulting in large inheritance hierarchies that can become hard to deal with. A design based on object composition will have less classes, but more objects. The behavior of such a system depends more on the interrelationships between objects rather then being defined in a particular class.

Bill Harlan:

If you have multiple classes supporting the same protocol or group of methods, would you not define a shared interface? If you have a shared interface, do you still need to use inheritance to share implementation? Could you not share implementations by containment?

Instead of coupling classes tightly by letting them extending each other, you should couple them loosely by letting them use each other interfaces. If you do this, there is little room left for many abstract classes.