Why prefer composition over inheritance?
There are two ways to reuse code:
- Inheritance (IS-A):
class Car extends Vehicle. The child gets the parent's code, but is tightly bound to it. - Composition (HAS-A):
class Car { private final Engine engine; }. The class holds other objects and forwards work to them.
Why composition usually wins:
- The fragile base class problem. A child depends on how the parent works inside. When the parent changes (or calls its own methods in unexpected ways), children break, as the example below shows.
- You inherit everything, even methods that make no sense for the child. The JDK's own
Stack extends Vectorlets you insert into the middle of a stack. - Only one parent. Inheritance uses up your one
extends; composition lets you combine many parts. - Flexibility at run time. Parts can be swapped (a different
PaymentGateway, a fake in tests); a parent class is fixed at compile time.
Use inheritance when the child truly is a special kind of the parent and the parent was designed for extension (framework base classes, exception hierarchies, sealed type hierarchies). Otherwise, compose.
Example
// Inheritance: looks right, counts wrong
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); // HashSet.addAll() calls add() for each element...
}
}
CountingSet<String> set = new CountingSet<>();
set.addAll(List.of("a", "b", "c"));
System.out.println(set.added); // 6, not 3: every element was counted twiceclass CountingSet<E> {
private final Set<E> set = new HashSet<>(); // HAS-A: we use a set, we aren't one
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); // whatever HashSet does inside can't affect our count
}
public boolean contains(Object o) { return set.contains(o); }
public int added() { return added; }
}@Service
class OrderService {
private final PaymentGateway payments; // swap Razorpay for another gateway, or a fake in tests
private final Notifier notifier;
OrderService(PaymentGateway payments, Notifier notifier) { // constructor injection = composition
this.payments = payments;
this.notifier = notifier;
}
}Common mistake
Extending a class just to reuse a few of its methods. You inherit its whole public API and every future change to its internals.
Under the hood
This example comes from Joshua Bloch's Effective Java (item "Favor composition over inheritance"). Many design patterns are composition in disguise: Strategy (swap an algorithm object), Decorator (new BufferedReader(new InputStreamReader(in)) wraps behaviour around another object) and Delegation. A good default is to make classes final unless you deliberately design and document them for extension.
Check yourself
CountingSet extends HashSet and counts in both add() and addAll(). After addAll(List.of("a","b","c")), what is the count?
How this connects
Know these first
Where this leads
Part of OOP in practice.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.