OOP · Design Principles
Composition over Inheritance
Keep the codebase lean and maintainable.
- Keep the codebase lean and maintainable.
- Review requirements before implementing new functionality.
Common Mistakes
- Developing features "just in case."
- Over-engineering simple applications.
- Adding options that no user requested.
- Increasing complexity without business value.
- Confusing flexibility with unnecessary functionality.
Real-World Problem
Imagine you're developing a Car Management System. Initially, the application contains a Car class and a PetrolCar subclass. Everything works correctly because the only supported engine is petrol.
As the company expands, new car models are introduced, including Electric Cars, Hybrid Cars, and Diesel Cars. Later, customers request optional features such as Sunroof, GPS Navigation, Music System, Airbags, and Parking Sensors.
To support every possible combination, developers keep creating subclasses such as PetrolCarWithGPS, DieselCarWithSunroof, ElectricCarWithMusicSystem, and many more. After some time, the number of classes grows rapidly, making the application difficult to maintain.
Problem Inheritance creates a rigid class hierarchy. Every new feature or variation often requires creating another subclass. As the number of combinations increases, the inheritance tree becomes large, complex, and difficult to manage.
Small requirement changes may force developers to modify or create multiple subclasses, reducing flexibility and increasing maintenance effort.
Pain Points
- Too many subclasses.
- Difficult to add new features.
- Rigid design.
- Poor maintainability.
- Code duplication increases.
- Changes affect multiple classes.
Solution

The Composition over Inheritance principle states that objects should achieve functionality by combining smaller reusable objects instead of relying heavily on inheritance.
Instead of creating subclasses for every feature combination, a class can contain objects that represent different functionalities. Features can be added, removed, or replaced without modifying the class hierarchy. Composition creates a more flexible and maintainable design because behavior is assembled rather than inherited.
Java Program 1 – Inheritance (Bad Design)

Java Program 2 – Composition (Good Design)
Benefits
- Provides greater flexibility.

Assemble behavior from parts (has-a) instead of locking into a rigid class tree (is-a).
// Instead of a Duck subclass hierarchy for every fly/quack combo,
// compose behaviors as pluggable parts.
interface FlyBehavior { void fly(); }
class FlyWithWings implements FlyBehavior {
public void fly() { System.out.println("Flying"); }
}
class NoFly implements FlyBehavior {
public void fly() { System.out.println("Can't fly"); }
}
class Duck {
private FlyBehavior flyBehavior; // HAS-A, swappable at runtime
Duck(FlyBehavior fb) { this.flyBehavior = fb; }
void performFly() { flyBehavior.fly(); }
}