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

  1. Inheritance represents an "is-a" relationship. A Dog is-a Animal. It creates a tight coupling between the base class and the subclass.
  2. Composition represents a "has-a" relationship. A Car has-an Engine. 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:

  1. Breaking LSP: The RobotDog violates the Liskov Substitution Principle by throwing an exception for a base class method.
  2. Tight Coupling: Any change in the Animal class 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:

  1. Runtime Flexibility: You can change an object's behavior at runtime by swapping the internal component.
  2. Single Responsibility: Each class does one thing well (e.g., SimpleBarker only handles barking).
  3. Easier Testing: You can easily mock IBarker to test the Dog class in isolation.
Share this lesson: