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)
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.