Java Omnibus
OOAD & Architecture / OOP foundations / Inheritance vs Composition in Java
OOP Foundations Java Order Management
OOP Foundations

Inheritance can express subtype relationships, but composition usually survives change better in real Java systems.

Inheritance is one of the first reuse techniques developers learn, and one of the first they later unlearn in overused form. In OOAD, the question is not whether inheritance is good or bad. The question is whether a design expresses a true ‘is-a’ relationship or whether it is really a case of assembling behavior from smaller collaborators.

Why This Topic Matters

Why it matters in real Java systems

Commerce systems change by variation: discounts vary, payment authorization varies, shipping policies vary, and customer messaging varies. Those variations often evolve independently. Deep inheritance trees turn that natural variation into rigid coupled structure. Composition lets you combine behaviors without pretending every variation is a subtype.

Core Explanation

The design idea behind this topic

Inheritance couples a subtype to the implementation and assumptions of its parent. That can be appropriate when the subtype truly preserves the parent’s promises and the hierarchy matches domain reality.

Composition instead says: build the object from collaborators or contained behavior units. In Java, this often leads to cleaner use of interfaces, policies, and delegation. It also tends to align better with dependency injection, testing, and module boundaries.

Most mature OOAD guidance favors composition by default, then uses inheritance deliberately where the domain genuinely supports it.

Concept Breakdown

Key ideas to hold onto

Concept

Inheritance is strongest when the subtype is stable

If a PremiumCustomer truly behaves as a specialized Customer with preserved expectations, inheritance can work.

Concept

Composition assembles behavior

If checkout depends on pricing, fraud checks, and notification policies, those are usually collaborators, not subclasses.

Concept

OOAD prefers flexibility with clarity

Composition helps when different responsibilities change for different reasons.

Java Example

Java example in the Order Management domain

Java example: discount behavior through composition instead of subclassing carts
package org.javaomnibus.ecommerce.foundations;

import java.math.BigDecimal;
import java.util.List;

public final class Cart {
    private final List lines;
    private final DiscountPolicy discountPolicy;

    public Cart(List lines, DiscountPolicy discountPolicy) {
        this.lines = List.copyOf(lines);
        this.discountPolicy = discountPolicy;
    }

    public Money total() {
        Money subtotal = lines.stream()
            .map(OrderLine::lineTotal)
            .reduce(Money.zero("USD"), Money::add);
        return discountPolicy.apply(subtotal);
    }
}

interface DiscountPolicy {
    Money apply(Money subtotal);
}

final class NoDiscountPolicy implements DiscountPolicy {
    @Override
    public Money apply(Money subtotal) {
        return subtotal;
    }
}

final class LoyaltyDiscountPolicy implements DiscountPolicy {
    @Override
    public Money apply(Money subtotal) {
        return subtotal.subtract(new Money(new BigDecimal("10.00"), "USD"));
    }
}

record OrderLine(String sku, int quantity, Money unitPrice) {
    Money lineTotal() {
        return unitPrice.multiply(quantity);
    }
}

record Money(BigDecimal value, String currency) {
    static Money zero(String currency) {
        return new Money(BigDecimal.ZERO, currency);
    }

    Money add(Money other) {
        return new Money(value.add(other.value), currency);
    }

    Money subtract(Money other) {
        return new Money(value.subtract(other.value), currency);
    }

    Money multiply(int quantity) {
        return new Money(value.multiply(BigDecimal.valueOf(quantity)), currency);
    }
}
Code Walkthrough

Step by step

  1. Cart does not need subclasses like LoyaltyCart or HolidayCart to vary pricing behavior.
  2. The variation lives in DiscountPolicy, which is easy to change independently.
  3. Testing becomes simpler because each policy is small and isolated.
  4. If more policies appear later, the core Cart abstraction does not need hierarchy changes.
Real-World Usage

Where this shows up in practice

  • Pricing, notification, and shipping decisions that vary independently
  • Framework-based Java systems where behavior is wired from injected collaborators
  • Modules that need extension without brittle inheritance contracts
Trade-Offs

When not to over-apply it

Composition can produce more collaborators and slightly more wiring. That is usually a worthwhile trade when the domain varies in more than one dimension, but it can feel heavier for small, fixed, stable models.

Common Mistakes

Frequent mistakes to watch for

  • Using inheritance for code reuse even when the subtype relationship is weak
  • Creating deep class trees for business variation that really belongs in strategy-like collaborators
  • Avoiding inheritance entirely, even when the hierarchy is truly domain-correct and stable
  • Forgetting that composition still requires good abstractions and responsibility boundaries
Related Pages

Related concepts


What To Read Next