Skip to content

13 · 32 · 6 min

Has a, rather than is a

Inheritance is the first tool taught and the one most often misused. The advice to prefer composition is not a style preference; it is about what each one couples together.

Where inheritance goes wrong

java
class Stack extends ArrayList<String> { }

A stack is not a list. It has inherited add(int index, String e), remove(0) and set(3, x), so anyone can insert into the middle and the stack discipline is gone. The interface promises operations the class cannot honour.

The other classic is the square and rectangle. A square is a rectangle in mathematics, and in code a Rectangle has independent setWidth and setHeight, which a square cannot support without violating what callers expect. That is the Liskov substitution principle failing, and it is failing because the relationship was modelled on vocabulary rather than on behaviour.

Composition

Hold the other object instead, and expose only what belongs:

java
class Stack {
    private final List<String> items = new ArrayList<>();

    public void push(String s) { items.add(s); }
    public String pop() {
        if (items.isEmpty()) throw new IllegalStateException("empty");
        return items.remove(items.size() - 1);
    }
    public boolean isEmpty() { return items.isEmpty(); }
}

Three methods, no way to corrupt it, and the list can be swapped for an array or a deque without any caller noticing.

The combinatorial problem

Inheritance is fixed at compile time, so varying two things multiplies the classes:

code
FlyingSwimmingAnimal, FlyingWalkingAnimal, SwimmingWalkingAnimal, ...

Composition makes them independent fields, and they can change at run time:

java
class Animal {
    private MoveBehaviour movement;
    void setMovement(MoveBehaviour m) { this.movement = m; }
}

That is the strategy pattern, and it exists precisely because inheritance cannot express "this varies".

When inheritance is right

It is not always wrong. Use it when the subclass genuinely substitutes for the base everywhere, the relationship is stable, and you control both sides.

java
abstract class Shape { abstract double area(); }
class Circle extends Shape { ... }
class Square extends Shape { ... }

Every shape has an area, no subclass needs to refuse an inherited operation, and nothing will change that.

InheritanceComposition
Couplingtight, to the parent's internalsloose, to an interface
Changeable at run timenoyes
Inherits unwanted membersyesno
Combinationsmultiply classesindependent fields

The test to apply

Before extending, ask: can the subclass honour every promise the base class makes? If any inherited method would have to be disabled, throw, or be documented as "do not call", the relationship is wrong.