C++ : Inheritance is Anti-Pattern

As I am also claiming since ages ago, inheritance as a program composition method is evil. There is no reason to use class inheritance. And I am not the inventor of this advice.

What more can we say? Ah yes, please read this little text first. Immediately. Then come back here.

Subclassing

Code reuse, aka Implementation Inheritance. This is the bad one. Some people believe that the purpose of inheritance is code reuse. That is wrong. Stated plainly, “inheritance is not for code reuse.”

TextDoc is NOT a Parser. Lazy contractor: “But here is this Parser class that was bought in from a third party and I am lazy and I “just inherit” it. So that I can analyze the text as clients do require.” If Parser changes TextDoc code must be recompiled too. But who cares. He is already on the next contract. Even worse; if Parser version 2.0.0 has a bug nothing changes and nobody notices TextDoc has a bug too inherited from a Parser. That is because TextDoc and a Parser are tightly coupled.

Subtyping

This is better.  The inheritance of base types aka interfaces for the purpose of their implementation. C++ has not “interface” construct. MSVC has one for you.  Please use it if you can.

Inheritance over there has its purpose. It is used to implement the behaviour. Behaviour is expressed as an interface.

final is C++ keyword since C++11 and its introduction is excellent news for people who wish to stop other people abusing inheritance. So that (for example) lazy contractor from use-case 1 cannot abuse our counter:

Usually mentioned with it, but actual not tightly coupled to inheritance is:

Polymorphism

A promise of substitutability: ie TextDoc and BinaryDoc are both Document. Thus we can “call them document”. We can generalize the code designs. But pay attention: this is just a promise. Very rarely one can find pure polymorphic behaviour in mature code. That requires two things mostly missing from most projects started in a rush: discipline and vision. (yes, a.k.a. “The Design”)

Above we do not inherit for code reuse. Instead, we “sub-type”. We capture the relationship from reality into the design and we implement it and use it. The functionopendoc does not care what kind of doc are we opening. It opens any subtype of the type Document. Current and future ones.

That is polymorphic behaviour. It allows us to use the behaviour of a base type (aka base class) while operating on derived types (aka derived classes).

Why exactly, do we declare and use IDocument interface?  Because that decouples us from the particular Document internals.  And most importantly that allows for feasible design changes. For example, we could decide in the future to be able to incorporate and use online documents.

Now new clients can use the new type of doc

And we do not require old users to recompile their legacy code using our library, just because we added yet another type of document.

Feasibility as a conclusion

Sometimes it is simply not possible to change anything in the code. That means, your management has understood there are very expensive changes, a.k.a rewrites. Not feasible.

In contrast to that, the code/design above also has one more very important quality: Resilience to change. It is feasibly changeable. Changing that code is easily manageable. From both developers and clients/users points of view.

That put together delivers feasible development. Thank you and goodbye. There is more? There is indeed; if you are interested that is.

Right path is not the easy one
The right path is not the easy one
%d bloggers like this: