DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

3 - Composition vs Inheritance — The Physics of Change

Composition vs Inheritance — The Physics of Change

Composition vs Inheritance — The Physics of Change

Why software systems break, bend, or survive over time


**Inheritance feels powerful at first.

Composition feels boring at first.

Over time, only one of them obeys the laws of physics.**

This is Part 3 of the series:

  1. Classes in C#: From First Principles to Architectural Mastery
  2. Abstract Class vs Interface — A Systems View
  3. Composition vs Inheritance — The Physics of Change

This article is about why systems change, where pressure accumulates, and how design choices amplify or dissipate that pressure.


Software Has Physics (Even If We Pretend It Doesn’t)

In the physical world:

  • Rigid structures crack
  • Flexible structures absorb energy
  • Over-constrained systems fail catastrophically

Software behaves the same way.

Inheritance and composition are not just patterns —

they are mechanical properties of your system.


Inheritance: Rigid Bonds

What inheritance really creates

Inheritance creates a strong, directional bond between types.

public abstract class ReportGenerator
{
    protected virtual void Validate() { }
    protected virtual void LoadData() { }
    protected virtual void Render() { }

    public void Generate()
    {
        Validate();
        LoadData();
        Render();
    }
}
Enter fullscreen mode Exit fullscreen mode

At first, this feels elegant.
One base class. One flow. Everyone benefits.

The hidden cost

Inheritance introduces:

  • Temporal coupling (order matters)
  • Behavioral coupling (changes ripple downward)
  • Semantic coupling (derived classes must “mean” the same thing)

This is rigidity.


The Fragile Base Class Problem (Revisited)

A small change in the base class:

protected virtual void Validate()
{
    if (!IsConfigured)
        throw new InvalidOperationException();
}
Enter fullscreen mode Exit fullscreen mode

Suddenly:

  • Derived classes break
  • Tests fail far away
  • Bugs appear without local changes

This is stress propagation.


Composition: Flexible Joints

What composition really creates

Composition creates replaceable connections.

public class ReportService
{
    private readonly IValidator _validator;
    private readonly IDataLoader _loader;
    private readonly IRenderer _renderer;

    public ReportService(
        IValidator validator,
        IDataLoader loader,
        IRenderer renderer)
    {
        _validator = validator;
        _loader = loader;
        _renderer = renderer;
    }

    public void Generate()
    {
        _validator.Validate();
        _loader.Load();
        _renderer.Render();
    }
}
Enter fullscreen mode Exit fullscreen mode

Nothing here inherits anything.
Everything collaborates.

Mechanical difference

  • Inheritance = welded joints
  • Composition = bolted joints

Bolts can be replaced. Welds must be cut.


Change as Energy

Every requirement change injects energy into the system.

The question is:

Where does that energy go?

With inheritance

  • Energy travels down the hierarchy
  • Breakage appears far from the change
  • Developers become afraid to touch base classes

With composition

  • Energy is localized
  • Components absorb change independently
  • Systems evolve incrementally

This is why composition scales better.


Why Inheritance Still Exists (And Should)

Inheritance is not evil.
It is highly specialized.

Use inheritance when:

  • There is a true is-a relationship
  • Behavior must be enforced
  • The hierarchy is shallow and stable
  • You control all derived types

Examples:

  • Stream
  • DbContext
  • ControllerBase

These are framework spines, not application logic.


The Rule Most Tutorials Skip

Inheritance models taxonomy.

Composition models behavior.

Taxonomies change slowly.

Behavior changes constantly.

Most business logic is behavior, not taxonomy.


The Liskov Stress Test

Ask this for every inheritance relationship:

If I replace the base type with the derived type everywhere, does the system still make sense?

If the answer is “mostly” or “sometimes” —

you’re storing tension in the system.


Composition Enables Orthogonality

With composition, behaviors vary independently.

public interface ICache { }
public interface IRetryPolicy { }
public interface IAuthorization { }

public class DataService
{
    public DataService(ICache cache, IRetryPolicy retry, IAuthorization auth) { }
}
Enter fullscreen mode Exit fullscreen mode

Try modeling this with inheritance.
You can’t — without explosion.

This is why inheritance trees grow sideways and collapse.


The Evolution Path of Mature Systems

Healthy systems follow this trajectory:

  1. Start simple (concrete classes)
  2. Introduce interfaces (boundaries)
  3. Use abstract classes sparingly (spines)
  4. Favor composition everywhere else
  5. Seal aggressively at the edges

This is entropy management.


Composition and Testability

Composition makes testing:

  • faster
  • more precise
  • more local

Inheritance makes testing:

  • broader
  • more coupled
  • more fragile

Tests are sensors.
If tests hurt, the structure is wrong.


The Physics Summary

Property Inheritance Composition
Rigidity High Low
Change propagation Global Local
Coupling Implicit Explicit
Testability Harder Easier
Evolution Risky Incremental

The Mental Model to Keep

Inheritance accumulates stress.

Composition dissipates stress.

You cannot eliminate change.
You can only decide where it breaks.


Final Takeaway

Junior developers ask:

“Why does everyone say prefer composition?”

Senior engineers answer:

“Because I’ve seen where inheritance breaks.”

Design for the physics of change,

not the aesthetics of diagrams.


✍️ Cristian Sifuentes

.NET / C# • Architecture • Systems Thinking

Software doesn’t fail because it’s wrong.

It fails because it can’t bend.

Top comments (0)