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;
}
// Awkward: does a DeliveryVan really "extend" an Engine?— the question this whole lesson is about.public class Engine {— the class.public void start() { System.debug('Engine running.'); }— its one behavior.}— closesEngine.- (blank line)
public class DeliveryVan extends Engine {— aDeliveryVanhas an engine — it isn't a kind of engine. Forcing this intoextendsfails the "is-a" test from the Inheritance lesson, even though it compiles.public Decimal cargoCapacityKg;— its own field.}— closesDeliveryVan.
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();
}
}
public class Engine {— unchanged from before.public void start() {— its method.System.debug('Engine running.');— its behavior.}— closes the method.}— closesEngine.- (blank line)
public class DeliveryVan {— no longerextends Engine.private Engine engine = new Engine();— holds anEngineas a field instead — a has-a relationship.public Decimal cargoCapacityKg;— its own field, same as before.- (blank line)
public void startVehicle() {— a method of its own.engine.start();— delegates to the engine's ownstart()method rather than inheriting it directly.}— closes the method.}— closesDeliveryVan.
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';
}
}
public class HeartRateMonitor {— a standalone class.public Integer getCurrentBpm() {— its one method.return 72; // placeholder for real sensor data— a stand-in value.}— closes the method.}— closesHeartRateMonitor.- (blank line)
public class Treadmill {— aTreadmillhas a heart rate monitor built in — it definitely isn't a kind of heart rate monitor.private HeartRateMonitor monitor = new HeartRateMonitor();— holds one as a field.- (blank line)
public String getWorkoutSummary() {— a method combining data from both classes.return 'Current heart rate: ' + monitor.getCurrentBpm() + ' bpm';— delegates tomonitorfor the reading.}— closes the method.}— closesTreadmill.
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.
Composition Over Inheritance Quiz
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.