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
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:
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:
FlyingSwimmingAnimal, FlyingWalkingAnimal, SwimmingWalkingAnimal, ...Composition makes them independent fields, and they can change at run time:
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.
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.
| Inheritance | Composition | |
|---|---|---|
| Coupling | tight, to the parent's internals | loose, to an interface |
| Changeable at run time | no | yes |
| Inherits unwanted members | yes | no |
| Combinations | multiply classes | independent 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.