Composition vs Inheritance
TL;DR
- Inheritance (“IS-A”): A child class extends a parent class, inheriting all its behaviors. It creates a rigid, tightly coupled hierarchy.
- Composition (“HAS-A”): A class contains instances of other classes to achieve its functionality, rather than inheriting from them.
- Best Practice: “Favor Composition over Inheritance” because it creates highly flexible, modular, and testable code.
Concept
In the 1990s, developers modeled everything with deep Inheritance trees. A Bird extended Animal, and a Penguin extended Bird. But what happens when you add a fly() method to Bird? Suddenly, the Penguin inherits fly(), which is a logical bug. You then have to hack the Penguin class to override fly() and throw an exception. The hierarchy becomes brittle.
Composition solves this by assembling behaviors like Lego bricks.
Instead of a Bird class, you create a FlyingBehavior class and a SwimmingBehavior class.
An Eagle class is composed of FlyingBehavior. A Penguin class is composed of SwimmingBehavior. If requirements change, you just swap the behaviors without breaking massive class hierarchies.
Examples
// --- ❌ THE INHERITANCE PROBLEM (IS-A) ---
class Employee {
public void calculatePay() { /* standard logic */ }
}
class Manager extends Employee {
// Must override, tightly coupled to parent logic
@Override
public void calculatePay() { super.calculatePay(); /* add bonus */ }
}
// --- ✅ THE COMPOSITION SOLUTION (HAS-A) ---
// Define behaviors as separate, swappable components
interface PaymentStrategy {
int calculate(int baseSalary);
}
class StandardPay implements PaymentStrategy {
public int calculate(int base) { return base; }
}
class BonusPay implements PaymentStrategy {
public int calculate(int base) { return base + 5000; }
}
class ModernEmployee {
// The employee "HAS-A" payment strategy.
private PaymentStrategy payStrategy;
public ModernEmployee(PaymentStrategy strategy) {
this.payStrategy = strategy;
}
public void calculatePay(int base) {
payStrategy.calculate(base);
}
// We can change the behavior dynamically at runtime!
public void promote() {
this.payStrategy = new BonusPay();
}
}
Interview Questions
Q: Why does Composition make Unit Testing easier?
A: With Inheritance, if you want to unit test a Manager, you are forced to test all the complex database connection logic hidden inside the parent Employee class. You cannot easily isolate the Manager.
With Composition, the ModernEmployee takes a PaymentStrategy interface in its constructor. During a Unit Test, you can simply pass in a “Mock” PaymentStrategy (using Mockito). This perfectly isolates the ModernEmployee logic, making the test fast, reliable, and independent.
Q: What is the Strategy Pattern?
A: The example shown above (passing a PaymentStrategy interface into a class) is the classic Strategy Design Pattern. It relies heavily on Composition. It defines a family of algorithms, encapsulates each one, and makes them interchangeable at runtime. It perfectly adheres to the Open/Closed Principle (Open for extension, Closed for modification).