Q: Composition vs Inheritance — when to use which?

Answer:

Rule of thumb: "Favor composition over inheritance" (Effective Java, Item 18).

Inheritance (extends)Composition (has-a)
Relationship"is-a""has-a"
CouplingTight — child depends on parent's implLoose — depends on interface
FlexibilityFixed at compile timeSwap at runtime
EncapsulationBreaks (subclass sees parent internals)Preserves
Multiple typesSingle inheritance onlyCompose any number

Inheritance Example (When It Goes Wrong)

// Classic broken example from Effective Java
class InstrumentedHashSet<E> extends HashSet<E> {
    private int addCount = 0;

    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);  // 💥 HashSet.addAll calls add() internally
    }
}
// addCount double-counts because addAll → add → addCount++

Subclass broke when parent's internal call chain changed. Fragile base class problem.

Composition Fix

class InstrumentedSet<E> implements Set<E> {
    private final Set<E> delegate;        // composed, not extended
    private int addCount = 0;

    InstrumentedSet(Set<E> delegate) { this.delegate = delegate; }

    @Override public boolean add(E e) {
        addCount++;
        return delegate.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return delegate.addAll(c);  // delegate's internal calls don't hit our add()
    }
    // ... forward other Set methods
}

Works regardless of which Set impl is wrapped (HashSet, TreeSet, etc).

When Inheritance IS Right

  • True "is-a" relationship (Dog extends Animal).
  • Designed-for-extension classes (e.g., AbstractList).
  • Within your own controlled hierarchy.
  • Template Method pattern.

When Composition Wins

  • Reusing behavior across unrelated types.
  • Need to swap implementations.
  • Avoiding deep hierarchies.
  • Multiple "behaviors" needed (Java has no multiple inheritance).

Strategy Pattern (Composition Done Right)

class PaymentService {
    private final PaymentGateway gateway;  // injected, swappable
    PaymentService(PaymentGateway g) { this.gateway = g; }
    void pay(Order o) { gateway.charge(o); }
}
// Swap Stripe ↔ PayPal without touching PaymentService

Liskov Substitution Test

If subclass can't fully replace parent without breaking behavior → don't inherit.

class Square extends Rectangle { ... }  // ❌ violates LSP
// Setting width changes height — surprises code that expects Rectangle