Clean&Reactoring
Entry
Created: 04 Feb 2026
Updated: 04 Feb 2026
Composition vs. Inheritance in C#: A Guide to Flexible Design
In object-oriented programming (OOP), the mantra "favor composition over inheritance" is a fundamental principle for building maintainable and scalable systems. While inheritance is often the first concept taught in C#, it can lead to rigid, fragile codebases if overused.
The Core Difference
- Inheritance represents an "is-a" relationship. A
Dogis-aAnimal. It creates a tight coupling between the base class and the subclass. - Composition represents a "has-a" relationship. A
Carhas-anEngine. It allows you to build complex functionality by combining smaller, independent objects.
The Bad Example: The Fragile Inheritance Chain
Using inheritance to share behavior often leads to the "Lollipop" problem or deep hierarchies that are hard to change. If you want a RobotDog that barks but doesn't eat, a standard inheritance tree breaks.
// BAD: Deep inheritance leads to rigid structures
public class Animal
{
public virtual void Eat() => Console.WriteLine("Eating...");
public virtual void Sleep() => Console.WriteLine("Sleeping...");
}
public class Dog : Animal
{
public void Bark() => Console.WriteLine("Woof!");
}
// What if we create a RobotDog?
// It shouldn't Eat(), but it's forced to because it inherits from Animal.
public class RobotDog : Dog
{
public override void Eat() => throw new NotSupportedException("Robots don't eat!");
}
Why this is bad:
- Breaking LSP: The
RobotDogviolates the Liskov Substitution Principle by throwing an exception for a base class method. - Tight Coupling: Any change in the
Animalclass ripples down to every subclass, potentially breaking unexpected things.
The Good Example: Flexible Composition
Instead of forcing a hierarchy, we define behaviors as interfaces or independent classes and "compose" our objects using them.
// GOOD: Using interfaces and composition for flexibility
public interface IMover { void Move(); }
public interface IBarker { void Bark(); }
public class SimpleBarker : IBarker
{
public void Bark() => Console.WriteLine("Woof! Woof!");
}
public class RobotBarker : IBarker
{
public void Bark() => Console.WriteLine("Beep-Boop Bark!");
}
public class Dog
{
private readonly IBarker _barker;
// We "Inject" the behavior (Composition)
public Dog(IBarker barker)
{
_barker = barker;
}
public void PerformBark() => _barker.Bark();
}
// Usage
var organicDog = new Dog(new SimpleBarker());
var cyberDog = new Dog(new RobotBarker());
Why this is better:
- Runtime Flexibility: You can change an object's behavior at runtime by swapping the internal component.
- Single Responsibility: Each class does one thing well (e.g.,
SimpleBarkeronly handles barking). - Easier Testing: You can easily mock
IBarkerto test theDogclass in isolation.