Back to Inheritance
Q2Question 2 of 3

Should you prefer composition or inheritance?

Key Concepts
  • Inheritance (IS-A) creates tight coupling: child depends on parent's internal implementation (fragile base class problem)
  • Composition (HAS-A) creates loose coupling: class holds capabilities as fields and delegates to them
  • Composition allows swapping implementations at runtime; inheritance is fixed at compile time
  • Composition supports multiple capabilities (has-a many things); inheritance is limited to single extends
  • Use inheritance only for true IS-A relationships with shared mutable state (SavingsAccount IS-A Account)
  • Use composition for everything else: the Gang of Four says favor composition over inheritance

Definition

Inheritance creates tight coupling (IS-A, fragile base class, single only). Composition creates loose coupling (HAS-A, swappable, multiple). Prefer composition. Use inheritance only for true IS-A relationships with shared state.

Two ways to reuse behavior in Java.

  • Inheritance (IS-A): class B extends class A, inheriting all fields and methods. Tight coupling: changing A may break B. Single inheritance only. Exposes all parent public methods.
  • Composition (HAS-A): class B holds an instance of A as a field and delegates to it. Loose coupling: B uses A's interface without inheriting its internals. Multiple compositions possible. Controls what is exposed.
  • Fragile base class problem: inheritance breaks when the parent changes its internal implementation.
  • Flexibility: composition allows swapping implementations at runtime via constructor injection. Inheritance is fixed at compile time.
  • Gang of Four rule: favor composition over inheritance.

Inheritance: SavingsAccount IS-A Account at National Bank of India. If Account adds a buggy method, SavingsAccount inherits the bug. Composition: TradeService HAS-A FeeCalculator. TradeService does not inherit FeeCalculator's methods; it uses them through a field. Swap FeeCalculator with DiscountedFeeCalculator at runtime. TradeService does not change.

Understanding the Concepts

Should you prefer composition or inheritance?

The Gang of Four says favor composition over inheritance. Here is why.

What is the fragile base class problem?

JAVA
public class Account {
    protected java.math.BigDecimal balance = java.math.BigDecimal.ZERO;
    public void deposit(java.math.BigDecimal amount) { balance = balance.add(amount); }
    public void depositAll(java.util.List<java.math.BigDecimal> amounts) {
        for (java.math.BigDecimal amount : amounts) { deposit(amount); }
    }
}

public class AuditedAccount extends Account {
    private int transactionCount = 0;
    @Override
    public void deposit(java.math.BigDecimal amount) {
        super.deposit(amount);
        transactionCount++;
    }
}
// depositAll([100, 200, 300]) -> transactionCount = 3 (correct now)
// But if Account changes depositAll() to NOT call deposit(), count breaks.
// The child's behavior depends on the parent's internal implementation: fragile.

How does composition fix this?

JAVA
public class AuditedAccount {
    private final Account account; // HAS-A (delegation)
    private int transactionCount = 0;

    public AuditedAccount(Account account) { this.account = account; }

    public void deposit(java.math.BigDecimal amount) {
        account.deposit(amount); // delegates
        transactionCount++;
    }

    public void depositAll(java.util.List<java.math.BigDecimal> amounts) {
        for (java.math.BigDecimal amount : amounts) { this.deposit(amount); }
        // We control the flow. Parent changes do not break us.
    }
}

What are the four reasons to prefer composition?

JAVA
// Reason 1: Flexibility (swap at runtime)
public class TradeService {
    private final FeeCalculator feeCalculator; // injected
    public TradeService(FeeCalculator feeCalculator) { this.feeCalculator = feeCalculator; }
}
// Production: new TradeService(new StandardFeeCalculator())
// Testing: new TradeService(new ZeroFeeCalculator())
// Promotion: new TradeService(new DiscountedFeeCalculator())

// Reason 2: Multiple capabilities
public class TradeService {
    private final FeeCalculator feeCalc;         // has-a
    private final NotificationService notifier;  // has-a
    private final AuditService auditor;          // has-a
}
// Inheritance: only extends one class.

// Reason 3: No fragile base class problem
// Composition delegates. Does not inherit internals.

// Reason 4: Controlled exposure
// Inheritance exposes ALL parent public methods.
// Composition exposes only what it chooses to delegate.

When should you still use inheritance?

  • True IS-A relationship: SavingsAccount IS-A Account (business domain truth).
  • Shared mutable state: balance field shared across account types.
  • Template Method Pattern: parent defines algorithm, child fills steps.
  • Framework requirement: extends HttpServlet.

How do they compare?

| Aspect | Inheritance | Composition |
|--------|------------|-------------|
| Coupling | Tight | Loose |
| Flexibility | Fixed at compile time | Swappable at runtime |
| Reuse count | Single extends | Multiple has-a |
| Exposure | All parent methods | Only delegated methods |
| Fragile base class | Yes | No |
| Use case | True IS-A with state | Everything else |

What should a senior engineer know?

  • In Spring, composition is the default: services have repositories, have other services, all injected via constructor.
  • Effective Java Item 18: favor composition over inheritance.
  • The Decorator pattern is a specific form of composition where the wrapper implements the same interface as the delegate.

Visual Overview

DIAGRAM
  INHERITANCE (IS-A)
  ┌────────────────────┐
  │ Account             │
  │  balance, deposit() │
  └──────────┬─────────┘
             │ extends (tight coupling)
  ┌──────────┴─────────┐
  │ AuditedAccount      │
  │  inherits ALL       │
  │  parent changes     │
  │  may break child    │
  └────────────────────┘

  COMPOSITION (HAS-A)
  ┌────────────────────┐
  │ AuditedAccount      │
  │  ┌────────────────┐│
  │  │ Account account ││  private field
  │  │ (delegated)     ││
  │  └────────────────┘│
  │  deposit() {       │
  │    account.deposit()│  delegates
  │    count++         │
  │  }                 │
  └────────────────────┘
  Parent changes: no impact.

Key Takeaway

The Gang of Four says favor composition over inheritance. Inheritance creates tight coupling: the child depends on the parent's internal implementation (fragile base class problem), can only extend one class, and exposes all parent public methods. Composition creates loose coupling: the class holds capabilities as fields, can have unlimited capabilities, controls what is exposed, and swaps implementations at runtime via constructor injection.

Use inheritance only when there is a true IS-A relationship with shared mutable state (SavingsAccount IS-A Account with shared balance field), or the framework requires it, or you need the Template Method Pattern. Use composition for everything else, which is the vast majority of cases.

Remember one sentence: favor composition over inheritance because composition gives flexibility (swap at runtime), avoids the fragile base class problem, supports multiple capabilities, and controls what is exposed.

---

Share
Use ← → arrow keys to navigate