Composition Over Inheritance
Why holding an object as a field usually beats extending it, and how to swap behaviour at runtime.
Open this lesson in the learning hubKey points
- Composition is HAS-A: the object holds another object and delegates to it. Inheritance is IS-A.
- Prefer composition. You can swap the part at runtime, test it on its own, and mix parts in any combination.
- Inheritance multiplies: two engine types times three body types means six classes you have to maintain.
- A subclass depends on parent internals. Change the parent and subclasses break oddly - the fragile base class problem.
- The JDK agrees. Streams wrap, collections wrap,
Comparatorcomposes. Very little of it is a deep hierarchy.
Example
import java.util.List;
public class Main {
interface Engine { String run(); }
static class Petrol implements Engine { public String run() { return "vroom"; } }
static class Electric implements Engine { public String run() { return "hummm"; } }
// Car HAS-A engine. It is not an engine.
static class Car {
private final String model;
private final Engine engine;
Car(String model, Engine engine) {
this.model = model;
this.engine = engine;
}
String drive() { return model + " goes " + engine.run(); }
}
public static void main(String[] args) {
List<Car> fleet = List.of(
new Car("Sedan", new Petrol()),
new Car("Hatch", new Electric()),
new Car("Proto", () -> "whoosh") // brand new behaviour, zero subclasses
);
fleet.forEach(c -> System.out.println(c.drive()));
}
}
If "is it really one?" needs an argument, hold it as a field instead of extending it.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.