Skip to content

Composition Over Inheritance


Definition

Composition vs Inheritance


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

When to Use Inheritance


Tips & Tricks

Composition Tips