If you've ever ordered a coffee from a café, you've already used the Decorator Design Pattern without realizing it.
You begin with a simple coffee. Then you may ask for milk. Next, you add sugar. Perhaps you finish it with whipped cream or chocolate syrup.
Notice something interesting. The coffee itself never changes. Instead, each topping simply adds something extra to the existing coffee.
This is exactly what the Decorator Design Pattern does.
The Decorator Pattern is a Structural Design Pattern whose purpose is:
Add new functionality to an existing object dynamically without modifying its original class.
Instead of creating dozens of subclasses for every possible combination of features, we simply "wrap" an existing object with another object that adds extra behavior.
Imagine visiting your favourite coffee shop.
The shop offers a plain coffee for ₹100.
Now you have several choices.
Instead of creating separate classes like:
...the Decorator Pattern lets us build the coffee one layer at a time.
Simple Coffee ↓ Milk ↓ Sugar ↓ Whipped Cream
Every new ingredient wraps the previous coffee and adds its own behaviour.
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
Every coffee in our system must be able to tell us two things:
public class SimpleCoffee : ICoffee
{
public string GetDescription()
{
return "Coffee";
}
public decimal GetCost()
{
return 100;
}
}
This is our original object.
It knows absolutely nothing about milk, sugar or cream.
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee coffee;
protected CoffeeDecorator(ICoffee coffee)
{
this.coffee = coffee;
}
public virtual string GetDescription()
{
return coffee.GetDescription();
}
public virtual decimal GetCost()
{
return coffee.GetCost();
}
}
Notice that the decorator itself also implements ICoffee.
More importantly, it contains another ICoffee.
This allows decorators to wrap decorators.
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee)
: base(coffee)
{
}
public override string GetDescription()
{
return coffee.GetDescription() + ", Milk";
}
public override decimal GetCost()
{
return coffee.GetCost() + 20;
}
}
Milk doesn't replace the coffee.
It simply asks the existing coffee for its price, then adds ₹20.
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee)
: base(coffee)
{
}
public override string GetDescription()
{
return coffee.GetDescription() + ", Sugar";
}
public override decimal GetCost()
{
return coffee.GetCost() + 10;
}
}
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
Console.WriteLine(coffee.GetDescription());
Console.WriteLine(coffee.GetCost());
Output:
Coffee, Milk, Sugar 130
Let's understand what happens internally.
Initially:
SimpleCoffee
After adding milk:
MilkDecorator
│
â–Ľ
SimpleCoffee
After adding sugar:
SugarDecorator
│
â–Ľ
MilkDecorator
│
â–Ľ
SimpleCoffee
The outermost object is now SugarDecorator.
When we call:
coffee.GetCost();
The call travels through every layer.
SugarDecorator.GetCost() ↓ MilkDecorator.GetCost() ↓ SimpleCoffee.GetCost() ↓ 100 ↑ Milk adds 20 ↑ Sugar adds 10 ↑ 130
Each decorator performs its own work before returning the final result.
Imagine your coffee shop keeps introducing new toppings.
If inheritance were used, you would eventually need classes like:
The number of subclasses grows rapidly.
This is known as the class explosion problem.
The Decorator Pattern completely avoids this issue by combining decorators dynamically at runtime.
The Decorator Pattern appears in many places within the .NET ecosystem.
FileStream
↓
BufferedStream
↓
StreamReader
Each stream wraps another stream while adding additional functionality.
Each layer enhances the previous one without modifying it.
| Inheritance | Decorator |
|---|---|
| Behaviour fixed at compile time | Behaviour added at runtime |
| Many subclasses | Few reusable decorators |
| Difficult to extend | Easy to extend |
| Can cause class explosion | Avoids class explosion |
| Less flexible | Highly flexible |
Choose the Decorator Pattern whenever:
The Decorator Pattern is one of the most elegant design patterns because it promotes composition over inheritance.
Rather than creating new subclasses every time a new feature is introduced, we simply wrap an existing object with another object that enhances its behaviour.
The coffee example perfectly demonstrates this idea:
Simple Coffee ↓ Milk ↓ Sugar ↓ Whipped Cream
Each decorator adds only one responsibility while keeping the original coffee completely unchanged.
Once you understand this simple example, you'll begin to recognize the Decorator Pattern in many real-world .NET classes such as BufferedStream, StreamReader, and numerous middleware and pipeline-based frameworks.
To keep every session productive and distraction-free, please follow these simple guidelines:
Following these guidelines helps you focus better and ensures I can deliver the best learning experience in every class.
I prefer to start with a short 10-minute free call so I can understand:
Why? Because course content, teaching pace, and fees all depend on your needs — there’s no “one-size-fits-all” pricing. Please leave your details below, and I’ll get back to you to arrange a convenient time for the call.
Note: Payment is made only after your first class, once you’re completely satisfied. However, fees paid after the first class are non-refundable. This helps maintain scheduling commitments and allows me to reserve your preferred time slot with full attention.