The Decorator Pattern in C# — Adding Superpowers Without Touching the Original Code
Imagine you own a small coffee shop . You start with a basic black coffee. Over time, customers ask for milk, sugar, caramel, or whipped cream.
Should you create a new class for every combination — MilkCoffee, CaramelCoffee, SugarMilkCoffee, CaramelWhipSugarCoffee?
That’s madness
Instead, what if you could wrap your base coffee with decorators that add new behavior dynamically — like adding toppings on a base drink?
That’s what the Decorator Pattern does.
The Core Idea
Decorator Pattern allows you to dynamically add new functionality to an object without modifying its structure.
In simple words —
It wraps an existing object to extend its behavior without changing the original class.
Step 1: Define the Base Component
We’ll start with a common interface for all coffee types.
public interface ICoffee
{
string GetDescription();
double GetCost();
}
This defines the core contract — every coffee must have a description and a cost.
Step 2: Create the Concrete Component
Let’s create our simple black coffee — the foundation.
public class SimpleCoffee : ICoffee
{
public string GetDescription()
{
return “Simple Black Coffee”;
}
public double GetCost()
{
return 2.0; // base price
}
}
At this point, your coffee shop sells one item — plain black coffee for $2.
Step 3: Create the Abstract Decorator
The decorator must look and act like a coffee but with added behavior.
So it also implements the same interface and wraps another ICoffee.
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee;
}
public virtual string GetDescription()
{
return _coffee.GetDescription();
}
public virtual double GetCost()
{
return _coffee.GetCost();
}
}
This base decorator doesn’t change anything — it’s just a pass-through wrapper.
But now, any class that inherits from CoffeeDecorator can override parts to add new flavor (literally).
Step 4: Add Concrete Decorators
Now let’s make individual decorators that add new behavior to the base coffee.
Milk Decorator
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return _coffee.GetDescription() + “, Milk”;
}
public override double GetCost()
{
return _coffee.GetCost() + 0.5;
}
}
Sugar Decorator
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return _coffee.GetDescription() + “, Sugar”;
}
public override double GetCost()
{
return _coffee.GetCost() + 0.2;
}
}
Caramel Decorator
public class CaramelDecorator : CoffeeDecorator
{
public CaramelDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return _coffee.GetDescription() + “, Caramel”;
}
public override double GetCost()
{
return _coffee.GetCost() + 0.8;
}
}
Step 5: Combine Decorators Dynamically
Now you can stack decorators any way you want — without modifying the original SimpleCoffee class.
public class Program
{
public static void Main()
{
ICoffee myOrder = new SimpleCoffee();
Console.WriteLine($”{myOrder.GetDescription()} - ${myOrder.GetCost()}”);
myOrder = new MilkDecorator(myOrder);
Console.WriteLine($”{myOrder.GetDescription()} - ${myOrder.GetCost()}”);
myOrder = new SugarDecorator(myOrder);
Console.WriteLine($”{myOrder.GetDescription()} - ${myOrder.GetCost()}”);
myOrder = new CaramelDecorator(myOrder);
Console.WriteLine($”{myOrder.GetDescription()} - ${myOrder.GetCost()}”);
}
}
Output:
Simple Black Coffee - $2
Simple Black Coffee, Milk - $2.5
Simple Black Coffee, Milk, Sugar - $2.7
Simple Black Coffee, Milk, Sugar, Caramel - $3.5
Each decorator adds new behavior on top of the previous one — no class explosion, no inheritance mess.
What Just Happened
You created one base coffee.
Then you decorated it dynamically at runtime.
Each decorator:
Wrapped the previous object.
Added new functionality (description, cost).
Followed the same interface.
The beauty? You can add or remove decorators anytime.
Real-Life Parallels
This pattern appears everywhere — even outside your coffee shop!
.NET Streams
Stream fileStream = new FileStream(”data.txt”, FileMode.Open);
Stream gzipStream = new GZipStream(fileStream, CompressionMode.Compress);
Stream cryptoStream = new CryptoStream(gzipStream, encryptor, CryptoStreamMode.Write);
Each stream wraps another, adding behavior (compression, encryption, etc.).
ASP.NET Middleware
Each middleware wraps the next — authentication, logging, caching — all applied dynamically.UI Elements
In frameworks like WPF or WinForms, decorators can be used to wrap controls with borders, scrollbars, or shadows.
Why Not Just Inheritance?
If we tried to model every coffee type using inheritance:
MilkCoffee,SugarCoffee,CaramelCoffee,MilkSugarCoffee,MilkSugarCaramelCoffee, …
you’d end up with hundreds of subclasses for every combination.
Decorator avoids that by letting you combine small, reusable behaviors dynamically.
When to Use Decorator Pattern
Use it when:
You want to add responsibilities dynamically without changing existing code.
You want to avoid deep inheritance hierarchies.
You want flexible, layered behavior that can be composed.
Avoid it when:
You have a simple object model — don’t overuse it for small scenarios.
You can easily handle variations with parameters or simple conditionals.
Final Analogy
Think of your favorite burger joint :
You order a base burger.
Then you add cheese, sauce, extra patty, pickles — each wraps your original order, enhancing it without altering the base burger recipe.
That’s Decorator Pattern in software — adding features without touching the base logic.
Key Takeaway
SimpleCoffee→ Base componentMilkDecorator,SugarDecorator,CaramelDecorator→ Layers adding flavorFinal result → A combination of behaviors built at runtime
The pattern’s true power lies in extensibility — the ability to add, remove, or reorder behavior without modifying the base code.


