The decorator pattern lets you add behaviour to an object at runtime without subclassing. Instead of creating one subclass for every combination of features, you compose decorators around a base object — each decorator wraps the previous one and adds its own layer of functionality. This makes the pattern more flexible than static inheritance, at the cost of more construction indirection.
Component
Define the interface and a plain base object that represents the simplest case:
csharp
// Component interface and base concrete component
public interface IPizza
{
string GetDescription();
double GetCost();
}
public class PlainPizza : IPizza
{
public virtual double GetCost() => 4.0;
public virtual string GetDescription() => "thin";
}Decorator Base Class
The abstract decorator also implements IPizza and holds a reference to the wrapped pizza. By default it delegates both methods — concrete decorators override only what they change:
csharp
// Abstract decorator — implements IPizza and wraps another IPizza.
// All concrete decorators inherit from this and override what they change.
public class ToppingDecorator : IPizza
{
protected IPizza _pizza;
public ToppingDecorator(IPizza pizza) => _pizza = pizza;
public virtual double GetCost() => _pizza.GetCost();
public virtual string GetDescription() => _pizza.GetDescription();
}Concrete Decorators
csharp
// Concrete decorators — each adds its cost and description on top of the wrapped pizza
public class Tomato : ToppingDecorator
{
public Tomato(IPizza pizza) : base(pizza) {}
public override double GetCost() => _pizza.GetCost() + 0.25;
public override string GetDescription() => _pizza.GetDescription() + ", Tomato";
}
public class Mozzarella : ToppingDecorator
{
public Mozzarella(IPizza pizza) : base(pizza) {}
public override double GetCost() => _pizza.GetCost() + 0.50;
public override string GetDescription() => _pizza.GetDescription() + ", Mozzarella";
}Usage
csharp
// Decorators compose at construction time — wrap as deep as needed
PlainPizza basic = new PlainPizza();
IPizza fancy = new Tomato(new Mozzarella(new PlainPizza()));
Console.WriteLine(basic.GetCost()); // 4
Console.WriteLine(basic.GetDescription()); // thin
Console.WriteLine(fancy.GetCost()); // 4.75
Console.WriteLine(fancy.GetDescription()); // thin, Mozzarella, Tomato