A lot has been said about why inheritance is bad. I’ll share my take on this subject and show that inheritance is not really needed most of the time.
Structural inheritance
It breaks encapsulation
Inheritance for the sake of properties reuse is not the way that was meant by inheritance in OOP, since the early days of Simula and Smalltalk. Such inheritance approach breaks the human metaphor. Nobody should have an access to my internals: stomach, brain, or lungs. It breaks object’s encapsulation, its fundamental feature. It breaks OOP.
It is fragile
Behavior and internal structure are orthogonal concepts. They change independently. If some object extends internal structure of another one today, it doesn’t mean things will stay the same tomorrow.
It is procedural
Inheritance based on internal structure inevitably promotes procedural outlook. Good objects just have no chance to survive in such an ill environment. They become nothing but a data structures.
My personal experience with huge hierarchies reflects exactly this point. One of the projects I was engaged to had a huge amount of classes representing each request that the application could handle. The terrible thing was that those classes were full of business-logic. Since there were no real objects, reflecting domain, business-logic in those request-classes was more like procedures operating upon some data. And since that inheritance was motivated solely by request-fields reuse, it was very unnatural and unmaintainable.
It often feels artificial
Entities resulted from applying inheritance in this procedural sense always seem artificial. They rarely reflect ubiquitous language, if ever. The reason is that any domain has natural seams, marking off different responsibilities, behaviors — it is exactly what makes one object different from another. Objects internal data inherently can not be used to distinguish objects — just because it is internal.
Behavioral inheritance
Inheritance for the sake of behavioral extension is the way to go. It’s like biology species taxonomy. It was built relying upon some mutual traits that all of that species share. For example, all mammals are vertebrates, endothermic and produce milk to feed their babies. Primate is a kind of mammal, but have well developed hands and feet, with fingers and toes. Humans are a kind of primate, but we have the ability to self-reflect.
This is a metaphor used in object thinking and it has an interesting consequence. It implies that concrete classes can not be inherited. Keeping in mind biological taxonomy metaphor, there are no concrete mammal instances. There are no concrete primates. There are cats, dogs, humans. The real entities are the leafs of the hierarchy. All the rest is an abstract classes or interfaces. Hence what I’m in favor of is way closer to the concept of subtyping, not inheritance.
Inheritance is rare
Say we have a Merchant entity. It can be approved and expired. Typical approach is to make a “status” property. But very soon we realize that behavior differs a lot. Approved merchant can do pretty much anything that our system allows, unlike an expired one. Hence we come up with two separate classes — one for an approved merchant and one for an expired merchant. The first impel would be to make a base merchant class with all the internal data that they share — name, address, etc. But, firstly, as I wrote at the beginning, this is wrong motive, and secondly, none of object internal data has to be modeled (and often isn’t) as object’s properties. So this inheritance is wrong in both ways.
Following behavioral object decomposition approach, it turns out that inheritance is not necessary in most of the time. Things that we used to consider to be related very closely — approved merchant and expired merchant, both being merchants — in reality do not share any behavior, so do not have mutual parent.
Inheritance hierarchies are small
So following this decomposition approach there would not be huge inheritance cascades unless we design biology taxonomy. The number of decorators, on the contrary, is big.
It’s all about decomposing your domain. When done right, it comes out naturally. It’s far from being easy though.
Wrapping it up
So do not extend concrete classes—only abstract. Decompose your problem space first, this results in small and rare inheritance hierarchies, with plenty of decorators.
This is a cross-posted version of this original post.
Top comments (9)
I would advise not relying too much on the metaphor of biological taxonomy, since it’s based on one critical point that is at odds with what you are advocating: biological inheritance involves no abstracts, but only concretes.
Also be careful with proliferation of decorators. That way lies madness (due to unmaintainable code).
Biological inheritance involves only abstracts. Cat is abstract, dog is abstract. They both are types, or interfaces. The cat Tom is concrete, it is an object. It doesn't inherit any type, while a cat does.
I have a different perspective on decorators. They bring big amount of composable, small and hence maintainable classes which is a crucial for any object-oriented piece of software.
Felis silvestris is a specific gene pool, Canis familiaris is a specific gene pool. The cat Tom is a specific part of that gene pool. (It's composition all the way down, so to speak)
As for decorators (in vast amounts), if they work for you then all the better.
Oh, I see what you mean now. I just don't view a Cat as a gene pool. I consider it from a behavioral perspective, rather then the one from an internal state. Probably from scientifically point of view your approach makes sense, but I'm not a geneticist, I'm a developer. What I'm looking for are useful abstractions. This one works for me and for somebody else, while it might not work for you.
Not a single example to prove your thesis. Bah!
There can not be a sufficient example. The topic is too broad. Anyway, what exactly do you want be to exemplify? I think I've made it crystal clear that implementation inheritance is bad, no?
No you didn't. To prove it you must show that all cases prove your theory and it's very easy to find counter-examples (of inheritance applied correctly). It seems that you've worked with bad uses of inheritance.
As a counter-example, you can look at Hadoop project and its use of inheritance:
github.com/apache/hadoop/search?ut...
You can take any of those classes and propose a better implementation without using inheritance. It's a top quality project, good luck!
I didn't intend to prove that. Programming is not mathematics, there is nothing that can be proved there. But there are good and bad practices. I showed why generally implementation inheritance is bad.
In order to fix Hadoop's inheritance issues I should realize what it is about internally, and I don't. Anyway, do you think that it's just impossible to replace inheritance with composition? And who told you that Hadoop is a top-quality project?
Here is how a world without (almost) any inheritance could look like: github.com/yegor256/takes
I had no idea Lenin, Marx and Engels were proponents of classical inheritance. I did hear that Stalin loved global variables.