DEV Community

Discussion on: Why composition is superior to inheritance as a way of sharing code

Collapse
 
cullophid profile image
Andreas Møller

Do you have an example where subtype polymorphism cannot be replaced by composition and interface polymorphism?

Collapse
 
pnambic profile image
Daniel Schmitt • Edited

One example would be a requirement to enforce behavioural invariants (one of the aspects of the Liskov substitution principle aka the L in SOLID). The compiler can't verify that you followed some rule in the dev manual that says, "all classes implementing IDependency must implement method x() by calling _dep.x(), and only _dep.x()". But, if you express that rule through final/non-virtual methods on a base class instead, then it can, and as a bonus it's self-documenting.

Another example, from a maintainability point of view: imagine having 42 classes implementing IDependency and a requirement to extend that interface with a new set of methods. Would you rather touch one or two files, or all 44 of them, even if the changes are mostly trivial? Java 8 added inheritance of default implementations to interfaces to support that exact scenario.

Lastly, imagine you would like to inherit several levels of bases because your problem domain is hierarchically structured, you need to conclusively demonstrate correctness in some parts, and want to leverage the type system to do that. So you end up with something like InvoicePaid (detailed business rule level) inheriting from TaxAccountable (legally required, with external code audit) inheriting from Financial (security rules about event routing) inheriting from Event (shared technical infrastructure).

Interface inheritance can usually deal with the correctness issue, because while the relevant behaviour depends on the type, it's typically implemented external to it (unlike in the first example), but imagine what a leaf class implementation would look like if these were more than just marker interfaces. And they will be, and you'll have lots of leaves.

What this boils down to is: every language supporting OO implementation design supports some form of implementation inheritance and also some form of composition (AFAIK), and there are good reasons for that. Traditionally, inheritance has been oversold by OO teaching materials, and overused as a result, but avoiding it completely is overreacting.