Many developers learn OOP through inheritance.
They learn:
class Animal
{
};
class Dog : public Animal
{
};and conclude:
Inheritance is the primary way to reuse code.
Unfortunately, this belief has caused some of the most rigid, fragile, and difficult-to-maintain software systems ever written.
For decades, software engineers heavily relied on inheritance.
Eventually they discovered a painful truth:
Inheritance is often the wrong abstraction.
This realization led to one of the most influential principles in software engineering:
Favor Composition Over Inheritance
Historical Context
During the 1980s and 1990s, OOP became mainstream.
Languages like:
- C++
- Java
- Smalltalk
made inheritance extremely popular.
Developers began creating massive inheritance trees.
Example:
Animal
│
├── Mammal
│ ├── Dog
│ ├── Cat
│ └── Lion
│
├── Bird
│ ├── Eagle
│ └── Sparrow
│
└── FishInitially:
Everything looked elegantThen real business requirements arrived.
Suddenly:
Dog need GPS tracking
Cat needs subscription plans
Lion needs zoo management
Bird needs migration trackingThe hierarchy became difficult to evolve.
Engineers discovered that inheritance creates:
Strong Couplingbetween parent and child classes.
This led to the modern preference:
Composition First
Inheritance SecondUnderstanding Inheritance
Inheritance models:
IS-A Relationship
Example:
class Animal
{
public:
void eat()
{
}
};
class Dog : public Animal
{
};Meaning:
Dog IS AN AnimalInheritance allows:
Code Reuse
Polymorphism
Shared BehaviorAt first glance:
Inheritance seems perfect.But hidden problems exist.
Understanding Composition
Composition models:
HAS-A Relationship
Example:
class Engine
{
};
class Car
{
private:
Engine engine;
};Meaning:
Car HAS AN EngineThe Engine is not a Car.
The Car is not an Engine.
They collaborate.
This simple differences changes everything.
The Core Difference
Inheritance says:
I Am YouComposition says:
I Use YouInheritance creates identity relationships.
Composition creates collaboration relationships.
Architecturally:
Collaboration
is usually more flexible than identity.Real-World Analogy
Consider a company.
Inheritance approach:
Employee
Manager extends Employee
Director extends Manager
VP extends Director
CEO extends VPNow suppose:
CEO role changesEvery descendant relationship becomes questionable.
Composition approach:
Person
+
RolePerson HAS A Role
Today:
Role = ManagerTomorrow:
Role = DirectorNo class hierarchy changes.
Behavior changes dynamically.
This is enormously powerful.
The Fragile Base Class Problem
One of the biggest inheritance issues:
Consider:
class DatabaseConnection
{
protected:
int timeout = 30;
public:
virtual void connect()
{
std::cout << "Connecting";
}
};Child:
class MySQLConnection
: public DatabaseConnection
{
};Everything works.
Six months later:
Parent changes:
timeout = 60;Unexpectedly:
MySQL behavior changesEven though nobody modified MySQLConnection.
This is called: Fragile Base Class Problem
A change in the parent can silently break children.
Why Inheritance Creates Coupling
Consider:
class Animal
{
};Child:
class Dog: public Animal
{
};Dog depends on:
Animal Data
Animal Behavior
Animal Assumptions
Animal LifecycleIf Animal changes:
Dog may breakThe dependency is extremely strong.
This is tight coupling.
Composition Reduces Coupling
Instead:
class BarkBehavior
{
public:
void bark()
{
std::cout << "Woof";
}
};class Dog
{
private:
BarkBehavior barkBehavior;
public:
void bark()
{
barkBehavior.bark();
}
};Dog uses behavior.
Dog does not become behavior.
Dependency becomes weaker.
The Bird Problem
A classic example:
Naive design:
class Bird
{
public:
virtual void fly()
{
}
};Derived:
class Eagle : public Bird
{
};Works.
Then:
class Penguin : public Bird
{
};Problem:
Penguins Cannot FlyWhat should Penguin do?
void fly() override
{
throw Exception();
}Terrible design.
The hierarchy is wrong.
Composition Solution
Separate behavior.
class FlyBehavior
{
public:
virtual void fly() = 0;
};class CanFly : public FlyBehavior
{
};class CannotFly : public FlyBehavior
{
};Bird becomes:
class Bird
{
private:
FlyBehavior* flyBehavior;
};Now:
Eagle->CanFly
Penguing->CannotFlyNo broken hierarchy.
Behavior becomes composable.
The Fundamental Question
Before using inheritance ask:
Am I modeling identity
or behavior?Identity:
Dog IS AN AnimalInheritance may work.
Behavior:
Dog CAN Bark
Dog CAN Run
Dog CAN SwimComposition is usually better.
Runtime Flexibility
Inheritance is fixed at compile time.
Example:
class FlyBehavior
{
public:
virtual void fly() = 0;
};Dog can never become:
CatComposition allows runtime changes.
Example:
dog.setBehavior(
new AggressiveBehavior());Later:
dog.setBehavior(
new FriendlyBehavior());No class changes.
Behavior evolves dynamically.
This is one reason composition dominates modern systems.
Example: Notification System
Bad inheritance design:
class Notification
{
};
class EmailNotification
: public Notification
{
};
class SMSNotification
: public Notification
{
};
class PushNotification
: public Notification
{
};Now add:
Priority
Encryption
Retries
SchedulingThe hierarchy explodes.
Composition:
Notification
+
DeliveryStrategy
+
RetryPolicy
+
EncryptionPolicyMix and match behaviors.
Much cleaner.
Example: Game Character System
Inheritance approach:
Character
│
├── Warrior
├── Mage
├── ArcherThen requirement arrive:
Flying Warrior
Invisible Warrior
Fire Warrior
Ice WarriorHierarchy explosion:
FlyingFireWarrior
InvisibleFireWarrior
FlyingIceWarriorImpossible to maintain.
Composition:
Character
+
AttackBehavior
+
MovementBehavior
+
VisibilityBehaviorStrategy Pattern: Composition in Practice
One of the most important design patterns emereges directly from composition.
Suppose we support multiple payment methods.
class PaymentStrategy
{
public:
virtual void pay() = 0;
};Implementations:
CreditCardPayment
UPIPayment
PayPalPayment
CryptoPaymentOrder class:
class Order
{
private:
PaymentStrategy* strategy;
};The Order object does not care how payment occurs.
It simply delegates behavior.
This is Strategy Pattern.
In many ways, Strategy is simply composition applied to behavior.
When Inheritance Is Appropriate
Composition is preferred.
However, inheritance remains valuable when:
A Genuine IS-A Relationship Exists
Example:
Shape
|
v
Circle
Rectangle
TriangleEvery child genuinely satisfied:
IS-A ShapeBehavior remains stable.
Warning Signs of Bad Inheritance
Step 1: Inheritance Used Only for Code Reuse
If the primary reason for inheritance is avoiding duplicate code, reconsider the design.
Composition often provides safe reuse.
Sign 2: Constant Method Overriding
If subclasses override most inherited methods, the hierarchy may be incorrect.
Sign 3: Exceptions for Parent Behavior
The Penguin example is a classic warning sign.
Sign 4: Exploding Hierarchies
Rapid growth in subclasses usually indicated behavioral variation that should be modeled through composition.
Sign 5: Frequently Changing Requirements
Inheritance struggles when requirements evolve rapidly.
Composition adapts more easily.
Why Modern Frameworks Favor Composition
Modern frameworks rarely encourage deep inheritance trees.
Examples include:
- Spring
- ASP.NET Core
- React
- Angular
- Qt
These frameworks are largely built from:
- Components
- Services
- Policies
- Middleware
- Plugins
All of these are forms of composition.
The industry moved in this direction because composition scales better as systems grow.
The Architect's Mindset
Beginners often ask
What should inherit from what?
Experienced architects ask:
What responsibilities exist?
How should those responsibilities collaborate?
Inheritance focuses on classification.
Composition focuses on behavior assembly.
Modern software is far more concerned with assembling behavior than organizing taxonomies.
That is why composition has become the dominant design approach.
Leave a comment
Your email address will not be published. Required fields are marked *


