Like for dependency injection, heritage and composition are easily misundertsood.
We remember that programs written in Oriented Object Programming (OOP ) are designed by making them out of objects that interact with one another. Technically, we have two possibilities to share code in an OOP style: either composition or inheritance.
The purpose of composition is to make wholes out of parts. These wholes (or components) will collaborate to achieve a common goal.
Composition has a natural translation in the real world. A car has 4 wheels. Without the wheels the car is still a car, but it loses its ability to move.
Back to code, a bank account might need a print service. Without the print service, the bank account is still a bank account, but it loses its ability to be printed. It is a “has a” relationship.
Inheritance allows expressing and ordering concepts from generalized to specialized in a classification hierarchy.
The meaning of a class is visible in the inputs and outputs (public contract) of the class. As a child of a class, we are implicitly accepting responsibility for the public contract of the superclass. We are tightly coupled to this superclass: it is a “is a” relationship.
A human being is a mammal. No exceptions, all mammals have a neocortex. Without the neocortex, we are no longer mammals. Back to code, I may design a “human” child class, inheriting a “mammal” base class, both sharing a NeoCortex property.
The problem’s root is that sharing code can be done either by composition or by inheritance, but the impacts are not the same.
Any class can be composed by other classes because it requires their capabilities.
But with inheritance, the key is to decide if we accept responsibilities from the public contract of the superclass. If our parent has some capabilities/properties we don’t need, we deny a part of the responsibility. The “is a” relationship is broken as soon as there is something in the parent that isn’t true for the child. Adding responsibilities in the parent because “some children might need it” is not ok. All children need it, or we need another abstraction, or we need composition.
It is a problem because inheritance implies tight coupling. A refactoring of a capability can impact all the children of a base class. If one of the children does not use the refactored capability, it should not be impacted.
Lots of modern languages use the concept of ViewModel, even if it may be called otherwise. It is appealing to build a ViewModelBase with everything any children might need, like the ability to be printed, or to show a dialog box.
What happens when a child inherits this ViewModelBase but doesn’t need to be printed or to show dialogs? It accepts responsibilities that does not make sense for it. The signature and the meaning of the class are blurred. Without these printing or show dialog ability, the ViewModel is still a ViewModel.
On the other hand, implementing a RaisePropertyChanged function in ViewModelBase makes sense, because any ViewModel is by definition glue between the business logic and the view. It needs the ability to inform the view when a property is updated. Without this ability, it’s no longer a ViewModel.
How to choose?
The mantra “favor composition over inheritance” is helpful, but as every mantra it is limited. It is true because sharing behaviours is always possible by composition, and will be more decoupled, but it is not always the wiser choice.
I think the “has a” vs “is a” relationship, and the reminder that every children must take the full responsibility of the parent in case of inheritance is enough to help choosing the best option.