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:
- Classes in C#: From First Principles to Architectural Mastery
- Abstract Class vs Interface — A Systems View
- 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();
}
}
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();
}
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();
}
}
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:
StreamDbContextControllerBase
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) { }
}
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:
- Start simple (concrete classes)
- Introduce interfaces (boundaries)
- Use abstract classes sparingly (spines)
- Favor composition everywhere else
- 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)