Inheritance
Inheritance is building a new class on an existing one, mostly to reuse code. The bargain has a price: a subclass inherits the entire interface of its parent, not a menu it gets to pick from, and it cannot hand any part of that back.
What breaks without it
Without inheritance, every class that needs behavior another class already has must either
duplicate that code or delegate to it by hand at every call site. A codebase with Invoice
and ExpenseReport as unrelated classes, both needing save(), print() and a
createdAt timestamp, either copies that logic twice - and now a bug fix has to land in two
places and someone will forget the second one - or wires up manual delegation everywhere,
which is inheritance's job done worse and by hand. Inheritance also gives you
substitutability for free: code written against Animal works on any Animal subclass
without modification, which is the foundation polymorphism is built on.
Worked example: reuse across siblings
A reporting tool models Invoice and ExpenseReport as subclasses of an abstract
Document. Both inherit save(), print() and a createdAt timestamp for free, and each
adds its own method: Invoice gets computeTax(), ExpenseReport gets
flagOverBudget().
The reuse is real, but so is the bargain: both subclasses are now stuck honoring every
method Document declares, whether or not it makes sense for them. If a DraftMemo
subclass is added later that should never actually be printed, silently no-op'ing
print() hides a real constraint from any caller that reasonably expects print() to do
something - a sign the hierarchy itself needs rethinking, not that DraftMemo should
quietly break the contract it inherited.
Worked example: one superclass, several interfaces
A class can extend only one superclass, but implement as many interfaces as it needs.
Cat extends Animal for its shared, concrete behavior, and separately implements
FourLegged and OxygenBreather for capabilities that have nothing to do with being an
Animal specifically.
Why not multiple class inheritance
Imagine a language allowed a class to extend two concrete parents, and a hypothetical
FlyingCar extended both Car and Airplane - both of which define refuel(), with
different logic.
There is no principled rule for which refuel() FlyingCar should get - this is the
diamond problem, and it's the reason most class-based languages cap concrete inheritance
at one parent. Interfaces sidestep the problem entirely: FourLegged and OxygenBreather
carry no competing implementation, so there is nothing for two of them to collide over.
Language mechanics: extends vs implements
extends inherits a real, concrete implementation - fields, method bodies, constructors -
from exactly one parent. implements promises to fulfill a contract with no inherited code
at all; a class can implement as many interfaces as it wants because promises don't
collide the way concrete method bodies do.
Overriding rules: a subclass overriding a method must keep the same signature (same
name, same or narrower parameter types depending on the language), cannot reduce the
method's visibility (a public method can't become protected in a subclass), and may
return a covariant type - a narrower return type than the parent declared, since anything
expecting the parent's return type can still accept the more specific one.
super calls: a subclass can call super.methodName() to invoke the parent's version
of an overridden method explicitly - useful when the subclass wants to extend the
parent's behavior rather than replace it outright, for example logging before calling
super.save() to still get the base save logic.
Constructor chaining: a subclass constructor always invokes a superclass constructor
before running its own body - explicitly with super(...), or implicitly, the language
calling the parent's no-argument constructor if you don't write the call yourself. This
guarantees the inherited part of the object is fully initialized before the subclass adds
anything on top:
Abstract classes vs concrete classes: an abstract class can declare methods with no
body (the subclass must fill them in) alongside methods with real implementations, and it
cannot be instantiated directly - new Document() is not legal if Document is abstract,
only new Invoice() or new ExpenseReport(). A concrete class must implement every
abstract method it inherits before it can be instantiated at all.
Common mistakes:
- Reaching for inheritance purely to reuse code when there is no real is-a relationship -
making
ExpenseReportextendInvoicejust because both happen to have atotalfield and asave()method, when neither is actually a kind of the other. The fields matching is a coincidence, not a taxonomy. - Building deep inheritance chains - four or five levels of subclassing to reuse a sliver of behavior at each level makes it genuinely hard to answer "where does this method actually come from" without reading every ancestor. Once a hierarchy gets that deep, composition (holding a reference to an object and delegating to it) usually expresses the same reuse with a much shorter chain to trace.
Where this shows up in the patterns catalogue
- Template Method is inheritance doing the deciding: the base class fixes the order of operations, and subclasses fill in individual steps by overriding them.
- Favoring composition once a hierarchy gets deep is the whole argument behind Decorator and Strategy - both add or swap behavior by holding a reference to another object instead of adding another layer of subclass.
Try it yourself: Document currently declares print() for every subclass - work
through what a DraftMemo subclass that is never meant to be printed should do: inherit
print() anyway and hope nobody calls it, override it to throw, or restructure the
hierarchy so printability isn't assumed for every Document. Then take the Employee /
Manager chain above and add a third level, RegionalManager extends Manager - write out
what its constructor has to call, in what order, for all three levels to end up correctly
initialized.
Check yourself
Cat implements both FourLegged and OxygenBreather, but extends only Animal. Why the asymmetry?