Composition vs Inheritance
Composition vs Inheritance
You now know how to build class hierarchies with extends. But inheritance is not always the right tool. In this lesson you will learn about composition — a different way to reuse code — and understand when each technique belongs.
is-a vs has-a
The single most useful question when deciding between inheritance and composition is:
- Is-a relationship? Use inheritance. A
Dogis-aAnimal. ASavingsAccountis-aBankAccount. - Has-a relationship? Use composition. A
Carhas-aEngine. APersonhas-aAddress.
When the relationship is genuinely hierarchical and substitution makes sense (anywhere a BankAccount is expected, a SavingsAccount works), inheritance is appropriate. When one class simply owns or uses another, composition is the better fit.
A Quick Example: the Wrong Way
Suppose you model a car by inheriting from Engine:
This compiles, but it is conceptually wrong. A Car is not a kind of Engine; it has one. Worse, every public method of Engine is now exposed on Car — callers can call car.start() thinking they are starting the car, but they are really reaching inside an implementation detail.
The Right Way: Composition
With composition, Car holds a reference to an Engine as a field:
Car delegates the starting behaviour to Engine through a method call, not through inheritance. Engine's internals are completely hidden from callers of Car.
The Fragile Base Class Problem
One of the most important reasons to prefer composition is the fragile base class problem: when you change a parent class, you can silently break all subclasses — even ones you never touched.
Here is a classic illustration. Imagine a custom list that counts how many elements were ever added:
Why 6 instead of 3? Because ArrayList.addAll internally calls add for each element. So CountingList.addAll increments by 3, and then super.addAll calls CountingList.add three more times, each incrementing again. The subclass broke because it depended on an internal implementation detail of the parent class — a detail that is not part of the public contract and could change in any future JDK release.
ArrayList, HashMap, or Stack almost always leads to this kind of subtle bug.
Fixing It with Composition
Wrap the list instead of extending it:
Now CountingList owns its counting logic entirely. Changes to ArrayList's internals cannot affect it.
Favoring Composition: the Guiding Principle
The phrase "favor composition over inheritance" comes from the classic book Design Patterns (the "Gang of Four"). It does not mean never use inheritance — it means use inheritance only when the is-a test passes convincingly and the base class was designed to be extended. In all other cases, compose.
- Composition is more flexible. You can swap the composed object at runtime (e.g., inject a different
Engine), which is the foundation of many design patterns like Strategy and Decorator. - Composition has tighter encapsulation. Internal details of the helper class do not leak into the outer class's API.
- Inheritance is still right sometimes. Modeling a true type hierarchy —
Shape/Circle/Rectangle— is exactly what inheritance is for. The key is honesty about the relationship.
Summary
Inheritance models is-a relationships; composition models has-a relationships. The fragile base class problem shows that extending concrete classes to reuse their code can break silently when the parent changes. Composition — holding a reference to a helper object and delegating to it — avoids this coupling, gives you a cleaner API, and makes your code easier to swap and test. In the next and final lesson of this tutorial you will apply everything you have learned by building a complete Shape hierarchy.