Intermediate 25 min read

Composition Over Inheritance

By the end of this lesson, you'll be able to:

  • Define composition: building a class out of other objects rather than extending a parent
  • Recognize when a "has-a" relationship is a better fit than "is-a"
  • Refactor an awkward inheritance hierarchy into composition

Prerequisites: "Polymorphism"

When inheritance stops fitting

// Awkward: does a DeliveryVan really "extend" an Engine?
public class Engine {
    public void start() { System.debug('Engine running.'); }
}

public class DeliveryVan extends Engine { // this reads wrong
    public Decimal cargoCapacityKg;
}
  1. // Awkward: does a DeliveryVan really "extend" an Engine? — the question this whole lesson is about.
  2. public class Engine { — the class.
  3. public void start() { System.debug('Engine running.'); } — its one behavior.
  4. } — closes Engine.
  5. (blank line)
  6. public class DeliveryVan extends Engine { — a DeliveryVan has an engine — it isn't a kind of engine. Forcing this into extends fails the "is-a" test from the Inheritance lesson, even though it compiles.
  7. public Decimal cargoCapacityKg; — its own field.
  8. } — closes DeliveryVan.

Composition: has-a instead of is-a

public class Engine {
    public void start() {
        System.debug('Engine running.');
    }
}

public class DeliveryVan {
    private Engine engine = new Engine();
    public Decimal cargoCapacityKg;

    public void startVehicle() {
        engine.start();
    }
}
  1. public class Engine { — unchanged from before.
  2. public void start() { — its method.
  3. System.debug('Engine running.'); — its behavior.
  4. } — closes the method.
  5. } — closes Engine.
  6. (blank line)
  7. public class DeliveryVan { — no longer extends Engine.
  8. private Engine engine = new Engine(); — holds an Engine as a field instead — a has-a relationship.
  9. public Decimal cargoCapacityKg; — its own field, same as before.
  10. (blank line)
  11. public void startVehicle() { — a method of its own.
  12. engine.start(); — delegates to the engine's own start() method rather than inheriting it directly.
  13. } — closes the method.
  14. } — closes DeliveryVan.

This reads correctly and stays flexible: swapping in an ElectricEngine later only means changing one field's type.

A real business example: Fitness (Gym Equipment Bundles)

public class HeartRateMonitor {
    public Integer getCurrentBpm() {
        return 72; // placeholder for real sensor data
    }
}

public class Treadmill {
    private HeartRateMonitor monitor = new HeartRateMonitor();

    public String getWorkoutSummary() {
        return 'Current heart rate: ' + monitor.getCurrentBpm() + ' bpm';
    }
}
  1. public class HeartRateMonitor { — a standalone class.
  2. public Integer getCurrentBpm() { — its one method.
  3. return 72; // placeholder for real sensor data — a stand-in value.
  4. } — closes the method.
  5. } — closes HeartRateMonitor.
  6. (blank line)
  7. public class Treadmill { — a Treadmill has a heart rate monitor built in — it definitely isn't a kind of heart rate monitor.
  8. private HeartRateMonitor monitor = new HeartRateMonitor(); — holds one as a field.
  9. (blank line)
  10. public String getWorkoutSummary() { — a method combining data from both classes.
  11. return 'Current heart rate: ' + monitor.getCurrentBpm() + ' bpm'; — delegates to monitor for the reading.
  12. } — closes the method.
  13. } — closes Treadmill.

Composition lets Treadmill reuse HeartRateMonitor's behavior without pretending they share a lineage they don't.

A rule of thumb

When deciding between extends and holding a field: say the relationship out loud. "A DeliveryVan is an Engine" sounds wrong; "a DeliveryVan has an Engine" sounds right. When "has-a" fits better than "is-a," reach for composition instead of inheritance — even though Apex would happily compile either one.

Exercise

Refactor this awkward inheritance (a Car extending a Radio) into composition: Car should hold a Radio as a field instead.

Show hint

private Radio radio = new Radio(); then delegate to it from a Car method.

Salesforce: ○ Not connected Connect
APEX

Execution Results

Run your code to see the result.

Composition Over Inheritance Quiz

1. Composition models which kind of relationship?

2. A Car extending a Radio passes the "is-a" test cleanly.

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

Composition builds a class by holding references to other objects ("has-a") instead of extending a parent class ("is-a") — often a more flexible fit when the relationship isn't a clean lineage.