Objects, Classes & OOP Design

Composition over Inheritance

Implementation inheritance couples you to a superclass's hidden self-use patterns; composition wraps an object behind your own stable interface. Unless a class was designed and documented for extension, wrap it — don't extend it.
  • Inheritance breaks encapsulation: subclasses depend on superclass internals (EJ 18)
  • Composition + forwarding gives reuse without the coupling
  • The decorator pattern is composition in action (Collections.unmodifiableList, buffered streams)
  • Use inheritance only for genuine is-a between classes designed for it
  • Wrapper classes don't work where identity or callbacks matter (SELF problem)
The composition fix: wrap and forward
public class InstrumentedSet<E> implements Set<E> {
    private final Set<E> inner;             // composition: HAS-A, not IS-A
    private int addCount = 0;

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

    @Override public boolean add(E e) {
        addCount++;
        return inner.add(e);                // forward
    }

    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return inner.addAll(c);             // inner's self-use is now irrelevant
    }
    // … remaining Set methods forward to inner
}

The wrapper works with any Set implementation and any decoration order — it is a decorator, like BufferedInputStream over FileInputStream (Io Streams) or Collections.synchronizedMap over any map. A reusable forwarding class (ForwardingSet) makes writing such decorators nearly free; pair it with interfaces + skeletal implementations (Interfaces).

The decision test from Effective Java: is every B really an A — would you be comfortable substituting B anywhere an A is used, forever? Java's own library answers "no" incorrectly in places (Stack extends Vector, Properties extends Hashtable) and has paid for it with frozen, bypassable APIs.

Sources
  • Effective Java (3rd ed.)Item 18
  • Core Java, Volume I: Fundamentals (13th ed.)Ch. 5.11 — Design Hints for Inheritance