Module 20 · OOP fundamentals · Lesson 3 of 5
Inheritance
A subclass reuses and specialises a parent class. Overriding and super, constructor chaining, single versus multiple inheritance and the diamond problem, and the fragile base class problem that makes composition the safer default.
Why it matters
Inheritance is the most visible feature of object-oriented languages and the most overused. Used well, it models genuine "is a kind of" relationships and lets frameworks call your code through a common type. Used as a shortcut for reusing code, it welds classes together so that a change in a parent silently breaks children. Knowing exactly what inheritance couples together is what lets you choose it deliberately, and recognise when composition is the better tool.
The idea
A subclass (child) extends a superclass (parent): it inherits the parent's fields and methods, can add its own, and can override inherited methods to specialise behaviour. Anywhere the parent type is expected, a child object can be used. That substitutability is the point; code reuse is a side effect.
Overriding, super and constructors
An overriding method replaces the parent's version for objects of the child class, and the choice is made at runtime by the object's actual class (the polymorphism lesson explains how). The child can still call the parent's version with super.method() to extend rather than replace it. Constructors are not inherited: a child's constructor must call a parent constructor first, so the parent part of the object is initialised before the child part, a sequence called constructor chaining. Calling overridable methods from a constructor is a classic trap, because the child's override runs before the child's own fields are initialised.
abstract class Notifier {
public final void notify(User u, String msg) { // fixed algorithm
String text = format(msg);
send(u, text);
}
protected String format(String msg) { return msg.strip(); }
protected abstract void send(User u, String text); // each subclass decides
}
class SmsNotifier extends Notifier {
@Override protected String format(String msg) {
return super.format(msg).substring(0, Math.min(160, msg.length())); // extend parent
}
@Override protected void send(User u, String text) { gateway.sms(u.phone(), text); }
}
Single, multiple and the diamond
Java and C# allow a class one parent (single inheritance) but many interfaces. C++ and Python allow multiple parents, which raises the diamond problem: if B and C both extend A and override the same method, and D extends both, which version does D get? C++ requires explicit resolution or virtual inheritance; Python resolves by a defined method resolution order. Languages with single inheritance plus interfaces (and default methods) avoid most of the ambiguity while keeping multiple types.
The fragile base class problem
A subclass depends not just on its parent's public methods but on how they are implemented. Suppose a subclass of a collection counts insertions by overriding both add and addAll. If the parent's addAll is implemented by calling add for each element, every bulk insert is counted twice; if the parent later changes addAll to stop calling add, the count silently becomes correct, or wrong in a new way. The subclass broke, or changed meaning, without its own code changing. That is the fragile base class problem: inheritance exposes implementation details across the class boundary.
| Inheritance | Composition | |
|---|---|---|
| Relationship | is a kind of | has a, or uses a |
| Coupling | Child depends on parent's implementation details | Holder depends only on the part's public interface |
| Flexibility | Fixed at compile time | Can swap the part at runtime |
| Reuse | All of the parent, wanted or not | Exactly the behaviour you delegate to |
| Use when | Genuine subtype; framework requires it; the parent is designed for extension | Most other reuse |
The Low-Level Design track's lesson on composition over inheritance takes this further. A useful rule from both tracks: design classes either for inheritance (documented extension points, final elsewhere) or prohibit it (mark them final or sealed).
Trade-offs
Inheritance gives substitutability and concise reuse for genuine hierarchies, and costs tight coupling, deep hierarchies that are hard to follow, and fragility when parents change. Composition costs a little more code (delegating methods) and gives loose coupling and runtime flexibility. Shallow hierarchies built around abstract parents designed for extension are the healthy middle.
In an interview
Name the relationship before drawing the arrow: "Every vehicle type is a Vehicle the lot can park, so Car, Bike and Truck extend Vehicle and override size; but pricing varies independently, so the lot holds a PricingStrategy instead of subclassing for each pricing rule." Choosing inheritance for one axis and composition for another is exactly what interviewers look for.
Check yourself
A CountingSet subclass overrides add (count++ then super.add) and addAll (count += n then super.addAll). Adding a list of 3 items with addAll reports a count of 6. Why?