Decorator Design Pattern
Decorator Design Pattern
The Decorator Pattern is a structural design pattern that allows behavior to be dynamically added to individual objects without modifying their code. It achieves this by wrapping the object inside another object (the decorator) that provides additional functionality.
---
When to Use the Decorator Pattern
When you need to extend the behavior of an object without modifying its original code.
When subclassing is not feasible due to class explosion or maintenance issues.
When different combinations of behaviors are required dynamically at runtime.
---
Key Components of the Decorator Pattern
1. Component (Interface or Abstract Class) – Defines the common contract.
2. Concrete Component – The original implementation of the component.
3. Decorator (Abstract Class) – Wraps the component and can modify its behavior.
4. Concrete Decorator – Implements additional functionality while delegating to the wrapped component.
---
Example: Coffee Shop (Without the Decorator Pattern)
Suppose we have a Coffee class, and we want to add milk and sugar options.
Naïve Approach: Using Subclasses
public class Coffee
{
public virtual string GetDescription() => "Coffee";
public virtual double GetCost() => 5.0;
}
public class CoffeeWithMilk : Coffee
{
public override string GetDescription() => base.GetDescription() + " + Milk";
public override double GetCost() => base.GetCost() + 1.5;
}
public class CoffeeWithSugar : Coffee
{
public override string GetDescription() => base.GetDescription() + " + Sugar";
public override double GetCost() => base.GetCost() + 0.5;
}
public class CoffeeWithMilkAndSugar : Coffee
{
public override string GetDescription() => base.GetDescription() + " + Milk + Sugar";
public override double GetCost() => base.GetCost() + 2.0;
}
Problems with This Approach
Class Explosion – Every new combination requires a new class.
Difficult to Extend – Adding a new ingredient (e.g., caramel) requires modifying multiple classes.
---
Refactored Using the Decorator Pattern
Step 1: Define the Component Interface
public interface ICoffee
{
string GetDescription();
double GetCost();
}
Step 2: Create a Concrete Component
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Coffee";
public double GetCost() => 5.0;
}
Step 3: Create an Abstract Decorator
public abstract class CoffeeDecorator : ICoffee
{
protected readonly ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee;
}
public virtual string GetDescription() => _coffee.GetDescription();
public virtual double GetCost() => _coffee.GetCost();
}
Step 4: Create Concrete Decorators
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => base.GetDescription() + " + Milk";
public override double GetCost() => base.GetCost() + 1.5;
}
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => base.GetDescription() + " + Sugar";
public override double GetCost() => base.GetCost() + 0.5;
}
Step 5: Usage (Dynamically Adding Behavior)
ICoffee coffee = new SimpleCoffee();
Console.WriteLine($"{coffee.GetDescription()} - ${coffee.GetCost()}");
// Add Milk
coffee = new MilkDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()} - ${coffee.GetCost()}");
// Add Sugar
coffee = new SugarDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()} - ${coffee.GetCost()}");
Output
Coffee - $5.0
Coffee + Milk - $6.5
Coffee + Milk + Sugar - $7.0
---
Benefits of the Decorator Pattern
✅ Flexible Behavior Extension – Modify objects dynamically.
✅ Open/Closed Principle – New decorators can be added without modifying existing code.
✅ Avoids Class Explosion – No need for multiple subclass combinations.
Drawbacks
❌ More Complexity – More classes and objects to manage.
❌ Order-Sensitive Behavior – The order of applying decorators may affect the result.
---
Real-World Use Cases
1. Logging Services – Adding logging to a service without modifying its original implementation.
2. Caching Repositories – Wrapping a repository to provide caching (as discussed earlier).
3. Authentication Middleware – Wrapping a request handler to add authentication before execution.
4. UI Components – Dynamically adding styles or behaviors to UI elements (e.g., tooltips, scrollbars).
---
Summary
The Decorator Pattern provides a way to extend object behavior without modifying the original class.
It is useful for dynamic behavior composition, such as adding logging, caching, or UI enhancements.
It helps reduce class explosion by combining behaviors at runtime instead of creating many subclasses.
Comments
Post a Comment