The Decorator Design Pattern is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. It’s used to extend or modify the functionality of objects at runtime, making it an alternative to subclassing for extending behavior.
Here’s a C# example of the Decorator Design Pattern with a use case for decorating a coffee order:
Create the Component Interface
Define an interface that represents the basic behavior or component that can be decorated.
public interface ICoffee
{
string GetDescription();
double GetCost();
}
Create the Concrete Component
Implement the component interface with a concrete class that provides the base functionality.
public class SimpleCoffee : ICoffee
{
public string GetDescription()
{
return "Simple Coffee";
}
public double GetCost()
{
return 2.0;
}
}
Create the Decorator Abstract Class
Define an abstract class (or interface) that extends the component interface and holds a reference to a component. This abstract class will serve as the base for concrete decorators.
public abstract class CoffeeDecorator : ICoffee
{
private readonly ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee;
}
public virtual string GetDescription()
{
return _coffee.GetDescription();
}
public virtual double GetCost()
{
return _coffee.GetCost();
}
}
Create Concrete Decorators
Implement concrete decorator classes that extend the decorator abstract class and add specific behavior.
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return base.GetDescription() + ", with Milk";
}
public override double GetCost()
{
return base.GetCost() + 0.5;
}
}
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return base.GetDescription() + ", with Sugar";
}
public override double GetCost()
{
return base.GetCost() + 0.2;
}
}
Use Case – Decorating a Coffee Order
Use the decorators to create customized coffee orders.
class Program
{
static void Main(string[] args)
{
ICoffee coffee = new SimpleCoffee();
Console.WriteLine($"Ordered: {coffee.GetDescription()}, Cost: ${coffee.GetCost()}");
// Decorate the coffee with milk and sugar
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
Console.WriteLine($"Ordered: {coffee.GetDescription()}, Cost: ${coffee.GetCost()}");
}
}
In this example, the Decorator pattern allows you to add additional functionality to a coffee order without altering the original SimpleCoffee class. Decorators can be stacked to create complex combinations of behavior. This pattern is useful when you want to extend the behavior of objects dynamically, promoting open-closed principles and code reusability.