Level 2 · 20 min
Composition vs Inheritance
"Favor object composition over class inheritance" is one of the most important principles from the GoF Gang of Four. Inheritance creates tight compile-time coupling; composition is more flexible and testable.
When Inheritance is Appropriate
Inheritance models an is-a relationship where LSP holds — substituting the subclass never breaks the base class contract. It's appropriate when: subclasses genuinely are specializations of the base, the hierarchy is shallow (2-3 levels max), and the base class is designed for extension (not just happened to have useful code). Extending to reuse code without an is-a relationship is abuse of inheritance.
Favor Composition
Composition (has-a) combines objects to achieve complex behavior. More flexible than inheritance: the composed object can be changed at runtime, composed classes can be tested independently, and no fragile base class problem. A Car has-a Engine rather than Car extends Engine. Composition is the mechanism behind Strategy, Decorator, and most GoF patterns. Bloch's Item 18 (Effective Java) demonstrates the hazard with the concrete InstrumentedHashSet example: a subclass of HashSet that counts insertions unexpectedly returns 6 after addAll(List.of("Snap","Crackle","Pop")) instead of 3, because HashSet.addAll internally calls add() — an implementation detail the subclass must not depend on. Bloch's conclusion: "Inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass... if you are tempted to have a class B extend class A, ask yourself the question: is every B really an A? If you cannot truthfully answer yes to this question, B should not extend A. B should contain a private instance of A and expose a different API."
Decorator via Composition
The Decorator pattern is composition in action — it wraps an object with the same interface and adds behavior. LoggingOrderService wraps OrderService and adds logging around all methods. This is far more flexible than LoggingOrderService extends OrderService — you can compose multiple decorators and change them at runtime without subclassing.
Code example
// Inheritance abuse: reusing code without is-a
class Stack<T> extends Vector<T> { // java.util.Stack mistake
// Stack IS-NOT-A Vector — it only uses 3 of Vector's 50 methods
// But now Stack exposes get(index), add(at, element) — broken abstraction
}
// Composition: Stack HAS-A storage
class Stack<T> {
private final Deque<T> storage = new ArrayDeque<>();
void push(T item) { storage.push(item); }
T pop() { return storage.pop(); }
T peek() { return storage.peek(); }
boolean isEmpty() { return storage.isEmpty(); }
// Only exposes Stack operations, not Deque's full API
}