Decorator Pattern in C#: What It Is and How It Works
Decorator Pattern in C# is a design pattern that lets you add new behavior to objects dynamically without changing their original code. It works by wrapping the original object inside a new object that adds the extra features.How It Works
Imagine you have a plain coffee, but you want to add milk or sugar without changing the coffee itself. The decorator pattern works the same way: it wraps an object with another object that adds new features. This wrapping can be done many times, each adding its own behavior.
In C#, this means you create a base interface or class, then create a decorator class that holds a reference to the original object. When you call a method, the decorator can do something extra before or after forwarding the call to the original object.
This pattern helps keep code flexible and clean because you don’t need to change existing classes to add new functionality.
Example
This example shows a simple coffee order system where decorators add milk and sugar to the coffee.
using System; // The base interface public interface ICoffee { string GetDescription(); double GetCost(); } // The original coffee class public class SimpleCoffee : ICoffee { public string GetDescription() => "Simple Coffee"; public double GetCost() => 2.0; } // Base decorator class public abstract class CoffeeDecorator : ICoffee { protected ICoffee _coffee; public CoffeeDecorator(ICoffee coffee) { _coffee = coffee; } public virtual string GetDescription() => _coffee.GetDescription(); public virtual double GetCost() => _coffee.GetCost(); } // Milk decorator public class MilkDecorator : CoffeeDecorator { public MilkDecorator(ICoffee coffee) : base(coffee) { } public override string GetDescription() => _coffee.GetDescription() + ", Milk"; public override double GetCost() => _coffee.GetCost() + 0.5; } // Sugar decorator public class SugarDecorator : CoffeeDecorator { public SugarDecorator(ICoffee coffee) : base(coffee) { } public override string GetDescription() => _coffee.GetDescription() + ", Sugar"; public override double GetCost() => _coffee.GetCost() + 0.3; } class Program { static void Main() { ICoffee coffee = new SimpleCoffee(); Console.WriteLine(coffee.GetDescription() + " costs $" + coffee.GetCost()); coffee = new MilkDecorator(coffee); Console.WriteLine(coffee.GetDescription() + " costs $" + coffee.GetCost()); coffee = new SugarDecorator(coffee); Console.WriteLine(coffee.GetDescription() + " costs $" + coffee.GetCost()); } }
When to Use
Use the decorator pattern when you want to add features to objects at runtime without changing their code. It is helpful when you have many combinations of features and want to keep your code clean and flexible.
For example, in user interface design, you might add borders, scrollbars, or shadows to windows dynamically. In logging, you might add extra information like timestamps or user info without changing the original logger.
Key Points
- The decorator pattern wraps an object to add new behavior.
- It keeps original classes unchanged and promotes code reuse.
- Multiple decorators can be combined to add layered features.
- It uses composition instead of inheritance for flexibility.