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.
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.
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.
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.