When should composition be preferred?
Solution and explanation
Principle
"Favour composition over inheritance" (Gang of Four, Effective Java). Use inheritance only for a genuine, stable IS-A relationship where the subclass truly is a specialised version of the parent. Use composition in most other cases.
Problems with inheritance
- Tight coupling: changes in the parent can break subclasses (fragile base class).
- Exposes everything: the child inherits all public methods, even unsuitable ones.
- Single inheritance: a class can only extend one class.
- Fixed at compile time: behaviour cannot be swapped at run time.
Classic example: counting additions
// Inheritance: broken
class CountingSet<E> extends HashSet<E> {
int added = 0;
@Override public boolean add(E e) { added++; return super.add(e); }
@Override public boolean addAll(Collection<? extends E> c) { added += c.size(); return super.addAll(c); }
}
CountingSet<String> s = new CountingSet<>();
s.addAll(List.of("a", "b", "c"));
System.out.println(s.added); // 6, not 3: HashSet.addAll internally calls add()
// Composition: correct and independent of HashSet's internals
class CountingSet<E> {
private final Set<E> set = new HashSet<>();
private int added = 0;
public boolean add(E e) { added++; return set.add(e); }
public boolean addAll(Collection<? extends E> c) { added += c.size(); return set.addAll(c); }
public int added() { return added; }
}
Swapping behaviour at run time (Strategy pattern)
interface DiscountStrategy { double apply(double price); }
class Checkout {
private DiscountStrategy discount; // HAS-A strategy
Checkout(DiscountStrategy d) { this.discount = d; }
void setDiscount(DiscountStrategy d) { this.discount = d; } // change at run time
double total(double price) { return discount.apply(price); }
}
Checkout c = new Checkout(p -> p); // no discount
c.setDiscount(p -> p * 0.9); // festive sale: 10% off
With inheritance you would need FestiveCheckout, ClearanceCheckout, PrimeFestiveCheckout… a class explosion.
Prefer composition when
- The relationship is "uses" or "has", not "is".
- You need behaviour from several sources.
- Behaviour should change at run time.
- You do not control the parent class (library classes).
- You want easy unit testing with mocks.
Inheritance is fine when
- There is a true IS-A relationship that satisfies Liskov substitution.
- The parent is designed and documented for extension (abstract base classes, template methods).
