Decorator Pattern | Structural Design Pattern

Decorator Pattern

The Decorator Pattern is a structural 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.


_____________
using System;

// Component interface
public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

// ConcreteComponent class
public class SimpleCoffee : ICoffee
{
    public string GetDescription()
    {
        return "Simple coffee";
    }

    public double GetCost()
    {
        return 1.0;
    }
}

// Decorator abstract class
public abstract class CoffeeDecorator : ICoffee
{
    private readonly ICoffee _decoratedCoffee;

    protected CoffeeDecorator(ICoffee decoratedCoffee)
    {
        _decoratedCoffee = decoratedCoffee;
    }

    public virtual string GetDescription()
    {
        return _decoratedCoffee.GetDescription();
    }

    public virtual double GetCost()
    {
        return _decoratedCoffee.GetCost();
    }
}

// ConcreteDecorator class
public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee decoratedCoffee)
        : base(decoratedCoffee)
    {
    }

    public override string GetDescription()
    {
        return base.GetDescription() + ", with milk";
    }

    public override double GetCost()
    {
        return base.GetCost() + 0.5;
    }
}

// ConcreteDecorator class
public class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(ICoffee decoratedCoffee)
        : base(decoratedCoffee)
    {
    }

    public override string GetDescription()
    {
        return base.GetDescription() + ", with sugar";
    }

    public override double GetCost()
    {
        return base.GetCost() + 0.2;
    }
}

class Program
{
    static void Main()
    {
        // Creating a simple coffee
        ICoffee simpleCoffee = new SimpleCoffee();
        Console.WriteLine("Description: " + simpleCoffee.GetDescription());
        Console.WriteLine("Cost: $" + simpleCoffee.GetCost());

        // Decorating the coffee with milk
        ICoffee milkCoffee = new MilkDecorator(simpleCoffee);
        Console.WriteLine("\nDescription: " + milkCoffee.GetDescription());
        Console.WriteLine("Cost: $" + milkCoffee.GetCost());

        // Decorating the coffee with sugar
        ICoffee sugarMilkCoffee = new SugarDecorator(milkCoffee);
        Console.WriteLine("\nDescription: " + sugarMilkCoffee.GetDescription());
        Console.WriteLine("Cost: $" + sugarMilkCoffee.GetCost());
    }
}
____________

In this example, SimpleCoffee is the base coffee class, and MilkDecorator and SugarDecorator are decorators. You can combine these decorators to add functionalities to the base object dynamically.


To understand it, we can draw an analogy between the Decorator Pattern and the concept of a superhero gaining new abilities. In this analogy:

SimpleCoffee is like a normal human (or a regular cup of coffee).

MilkDecorator and SugarDecorator are like superpowers that can be added to the normal human (or coffee) to enhance their capabilities.


So, just as Spider-Man gains new abilities like wall-crawling or enhanced strength, a simple coffee can be decorated with new behaviors, such as having milk or sugar added. Each decorator adds a specific behavior (or power) to the base object, allowing you to dynamically combine and extend functionalities without altering the original class.

It's a helpful way to understand how decorators provide flexibility and extensibility in a design, similar to how superheroes can adapt and gain new powers as needed.

Let's create a simple example using the analogy of a normal person (human) and a superhero (Spider-Man) with the Decorator Pattern in C#:



  __________
  
  
  using System;

// Component interface
public interface IHuman
{
    string Display();
}

// ConcreteComponent class
public class NormalHuman : IHuman
{
    public string Display()
    {
        return "I am a normal human.";
    }
}

// Decorator abstract class
public abstract class SuperhumanDecorator : IHuman
{
    private readonly IHuman _decoratedHuman;

    protected SuperhumanDecorator(IHuman decoratedHuman)
    {
        _decoratedHuman = decoratedHuman;
    }

    public virtual string Display()
    {
        return _decoratedHuman.Display();
    }
}

// ConcreteDecorator class
public class SpidermanDecorator : SuperhumanDecorator
{
    public SpidermanDecorator(IHuman decoratedHuman)
        : base(decoratedHuman)
    {
    }

    public override string Display()
    {
        return base.Display() + " Now, I have the powers of Spider-Man!";
    }
}

class Program
{
    static void Main()
    {
        // Creating a normal human
        IHuman normalHuman = new NormalHuman();
        Console.WriteLine(normalHuman.Display());

        // Enhancing the human with Spider-Man powers
        IHuman spidermanHuman = new SpidermanDecorator(normalHuman);
        Console.WriteLine(spidermanHuman.Display());
    }
}
  ___________
  



In this example:

NormalHuman is like a regular person.

SuperhumanDecorator is the abstract decorator class.

SpidermanDecorator is a specific decorator adding Spider-Man powers.

When we run this program, we'll see the transformation from a normal human to a superhuman with Spider-Man powers. This illustrates how decorators can dynamically add capabilities to an object without modifying its original code.



The Decorator Pattern is useful when you need to:


1. Add functionality to individual objects dynamically: Instead of using subclassing to create all possible combinations of features, you can use decorators to add or override behaviors at runtime.


2. Extend behavior without modifying existing code: It allows you to augment the behavior of objects without altering their code, making it easier to maintain and adhere to the Open/Closed Principle (classes should be open for extension but closed for modification).


3. Compose objects with different combinations of behaviors: You can combine different decorators to create various configurations, providing a flexible way to assemble objects with different features.


4. Avoid a "fat" class with too many responsibilities: Rather than having a single class with a multitude of methods, each responsible for different variations of behavior, decorators allow you to divide responsibilities among smaller, more focused classes.


Use cases for the Decorator Pattern include:


GUI components: Adding borders, scrollbars, or other decorations to graphical components.

  

Input/output streams: Adding functionalities like buffering, encryption, or compression to streams.


Text processing: Enhancing text with decorators for formatting, spell checking, or encryption.


Web frameworks: Applying filters or additional features to web requests or responses dynamically.


In essence, the Decorator Pattern is beneficial when we want to build flexible and reusable systems by allowing the addition of new behaviors to objects in a modular and dynamic way.


Comments