Great post on inheritance from the Sicpers blog, and why — as a concept — it often fails in OO languages such as Java.
This blog post can help you understand why:
- “Cat and Dog extend Animal” is poor use of OO.
- “Square extends Rectangle” is poor use of OO.
From my own experience, I’ve moved to primarily using inheritance in only two ways:
Modular Behaviors as Interfaces
My emphasis here is on identifying specific behavioral roles/ interactions of an entity, and putting an interface on them.
Essentially Liskov substitution, but focusing on behavior rather than state modelling and breaking the assumption that all roles & behaviors of the entity should be bundled together.
I find it increasingly rare, in real & evolving systems, that entire entities can be substituted and their behaviors, with regard to multiple other components, remain perfectly aligned.
Breaking apart distinct interactions into separate interfaces allows OO design & reuse to get a lot simpler — no longer must features which should be separate, be reused in monolithic lockstep.
The only feature lost, when you ditch these assumptions, is an implicit notion of object identity. That is easy to code explicitly in the API if ever needed.
Code Reuse via Concrete Subclasses
While I generally prefer system-wide substitutability to be exposed as interfaces, within the implementation there’s still a role for concrete inheritance in code reuse.
Defining substitutability via an interface allows implementations to reuse code where possible, compose or start afresh where not; while still fulfilling the interface.