Insights into Company Bills & Payments
← Java

OOP Principles

Understand the idea. Compare it. Explain it. Test yourself.

Question 11 of 11

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

  1. Tight coupling: changes in the parent can break subclasses (fragile base class).
  2. Exposes everything: the child inherits all public methods, even unsuitable ones.
  3. Single inheritance: a class can only extend one class.
  4. 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).
Practice explaining this

Showing questions 11–11 of 11