Updated on 22 Jun, 202628 mins read 19 views

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
โ”‚
โ””โ”€โ”€ Fish

Initially:

Everything looked elegant

Then real business requirements arrived.

Suddenly:

Dog need GPS tracking
Cat needs subscription plans
Lion needs zoo management
Bird needs migration tracking

The hierarchy became difficult to evolve.

Engineers discovered that inheritance creates:

Strong Coupling

between parent and child classes.

This led to the modern preference:

Composition First
Inheritance Second

Understanding Inheritance

Inheritance models:

IS-A Relationship

Example:

class Animal
{
public:
    void eat()
    {
    }
};

class Dog : public Animal
{
};

Meaning:

Dog IS AN Animal

Inheritance allows:

Code Reuse
Polymorphism
Shared Behavior

At 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 Engine

The 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 You

Composition says:

I Use You

Inheritance 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 VP

Now suppose:

CEO role changes

Every descendant relationship becomes questionable.

Composition approach:

Person
+
Role

Person HAS A Role

Today:

Role = Manager

Tomorrow:

Role = Director

No 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 changes

Even 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 Lifecycle

If Animal changes:

Dog may break

The 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 Fly

What 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->CannotFly

No broken hierarchy.

Behavior becomes composable.

The Fundamental Question

Before using inheritance ask:

Am I modeling identity
or behavior?

Identity:

Dog IS AN Animal

Inheritance may work.

Behavior:

Dog CAN Bark
Dog CAN Run
Dog CAN Swim

Composition is usually better.

Runtime Flexibility

Inheritance is fixed at compile time.

Example:

class FlyBehavior
{
public:

    virtual void fly() = 0;
};

Dog can never become:

Cat

Composition 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
Scheduling

The hierarchy explodes.

Composition:

Notification
+
DeliveryStrategy
+
RetryPolicy
+
EncryptionPolicy

Mix and match behaviors.

Much cleaner.

Example: Game Character System

Inheritance approach:

Character
โ”‚
โ”œโ”€โ”€ Warrior
โ”œโ”€โ”€ Mage
โ”œโ”€โ”€ Archer

Then requirement arrive:

Flying Warrior

Invisible Warrior

Fire Warrior

Ice Warrior

Hierarchy explosion:

FlyingFireWarrior

InvisibleFireWarrior

FlyingIceWarrior

Impossible to maintain.

Composition:

Character
    +
AttackBehavior
    +
MovementBehavior
    +
VisibilityBehavior

Strategy 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
CryptoPayment

Order 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
Triangle

Every child genuinely satisfied:

IS-A Shape

Behavior 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.

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *