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

Popular posts from this blog

Maxpooling vs minpooling vs average pooling

Understand the Softmax Function in Minutes

Percentiles, Deciles, and Quartiles