Composition Over Inheritance
Definition

Inheritance Problems
// PROBLEM 1: Fragile Base Class
public class ArrayList<E> {
public void add(E element) { ... }
public void addAll(Collection<E> c) {
for (E e : c) {
add(e); // Calls add() for each element
}
}
}
public class CountingList<E> extends ArrayList<E> {
private int addCount = 0;
@Override
public void add(E element) {
addCount++;
super.add(element);
}
@Override
public void addAll(Collection<E> c) {
addCount += c.size(); // Count the elements
super.addAll(c); // But super.addAll calls add()!
}
// BUG: addCount is incremented twice per element!
}
// PROBLEM 2: Inflexible Hierarchy
//
// Initial design:
// Animal → Bird → FlyingBird → Sparrow
//
// Now we need Penguin (Bird that can't fly)
// And Bat (Mammal that can fly)
// The hierarchy breaks!
// PROBLEM 3: Inherited Behavior You Don't Want
public class Stack<E> extends ArrayList<E> {
public void push(E item) { add(item); }
public E pop() { return remove(size() - 1); }
// Oops! Stack inherits get(index), set(index), etc.
// Violates stack semantics!
}
Composition Solution
// SOLUTION: Composition instead of inheritance
// Instead of CountingList extends ArrayList
public class CountingList<E> implements List<E> {
private final List<E> delegate = new ArrayList<>(); // HAS-A
private int addCount = 0;
@Override
public boolean add(E element) {
addCount++;
return delegate.add(element);
}
@Override
public boolean addAll(Collection<E> c) {
addCount += c.size();
return delegate.addAll(c); // No double counting!
}
// Delegate other methods
@Override
public int size() { return delegate.size(); }
@Override
public E get(int index) { return delegate.get(index); }
// ... other List methods
}
// SOLUTION: Flexible behavior via composition
public class Bird {
private final FlyingBehavior flyingBehavior;
private final SwimmingBehavior swimmingBehavior;
public Bird(FlyingBehavior flying, SwimmingBehavior swimming) {
this.flyingBehavior = flying;
this.swimmingBehavior = swimming;
}
public void fly() { flyingBehavior.fly(); }
public void swim() { swimmingBehavior.swim(); }
}
// Now we can create:
Bird sparrow = new Bird(new CanFly(), new CannotSwim());
Bird penguin = new Bird(new CannotFly(), new CanSwim());
Bird duck = new Bird(new CanFly(), new CanSwim());
// SOLUTION: Proper Stack using composition
public class Stack<E> {
private final Deque<E> deque = new ArrayDeque<>(); // HAS-A
public void push(E item) { deque.addFirst(item); }
public E pop() { return deque.removeFirst(); }
public E peek() { return deque.peekFirst(); }
public boolean isEmpty() { return deque.isEmpty(); }
// Only exposes stack operations!
}
Strategy Pattern Example
// Composition enables runtime behavior changes
// Payment processing with composition
public interface PaymentStrategy {
void pay(Money amount);
}
public class CreditCardPayment implements PaymentStrategy {
private final String cardNumber;
public void pay(Money amount) {
// Process credit card
}
}
public class PayPalPayment implements PaymentStrategy {
private final String email;
public void pay(Money amount) {
// Process PayPal
}
}
public class Order {
private PaymentStrategy paymentStrategy; // HAS-A
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout() {
paymentStrategy.pay(calculateTotal());
}
}
// Usage - can change at runtime!
Order order = new Order();
order.setPaymentStrategy(new CreditCardPayment("1234..."));
order.checkout();
// Later, same order object can use different payment
order.setPaymentStrategy(new PayPalPayment("[email protected]"));
order.checkout();
// With inheritance, this flexibility would be impossible
Decorator Pattern Example
// Composition allows adding behaviors dynamically
public interface Coffee {
double getCost();
String getDescription();
}
public class SimpleCoffee implements Coffee {
public double getCost() { return 2.0; }
public String getDescription() { return "Coffee"; }
}
// Decorator using composition
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee wrapped; // HAS-A
public CoffeeDecorator(Coffee coffee) {
this.wrapped = coffee;
}
}
public class Milk extends CoffeeDecorator {
public Milk(Coffee coffee) { super(coffee); }
public double getCost() { return wrapped.getCost() + 0.5; }
public String getDescription() {
return wrapped.getDescription() + ", Milk";
}
}
public class Sugar extends CoffeeDecorator {
public Sugar(Coffee coffee) { super(coffee); }
public double getCost() { return wrapped.getCost() + 0.25; }
public String getDescription() {
return wrapped.getDescription() + ", Sugar";
}
}
// Combine behaviors dynamically!
Coffee order = new Sugar(new Milk(new SimpleCoffee()));
// "Coffee, Milk, Sugar" - $2.75
// Try doing this with inheritance...
// CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar,
// CoffeeWithDoubleMilk... explosion!
When to Use Inheritance

Tips & Tricks
