DEV Community

Muhammad Salem
Muhammad Salem

Posted on

Decorator Pattern

Step-by-Step Guide to Implementing the Decorator Pattern

The Decorator Pattern is a structural design pattern that allows you to dynamically add behavior to an object without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.

1. Understand the Concept

The Decorator Pattern involves the following key components:

  • Component Interface: An abstract class or interface that defines the operations.
  • Concrete Component: A class that implements the Component interface.
  • Decorator: An abstract class that implements the Component interface and has a reference to a Component object.
  • Concrete Decorators: Classes that extend the Decorator class and add functionalities to the Component.

2. Define the Example Scenario

Let's consider a real-world example: A coffee shop where you can order different types of coffee and add various condiments (like milk, sugar, and whipped cream) dynamically.

3. Implementing the Example

Step 1: Define the Component Interface

First, create an interface or abstract class for the coffee:

Step 2: Create Concrete Components

Next, create concrete implementations of the Coffee class:

public class Espresso : Coffee
{
    public override string GetDescription()
    {
        return "Espresso";
    }

    public override double Cost()
    {
        return 1.99;
    }
}

public class HouseBlend : Coffee
{
    public override string GetDescription()
    {
        return "House Blend Coffee";
    }

    public override double Cost()
    {
        return 0.89;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Decorator Abstract Class

Create an abstract decorator class that implements the Coffee interface and holds a reference to a Coffee object:

public abstract class CoffeeDecorator : Coffee
{
    protected Coffee _coffee;

    public CoffeeDecorator(Coffee coffee)
    {
        _coffee = coffee;
    }

    public override string GetDescription()
    {
        return _coffee.GetDescription();
    }

    public override double Cost()
    {
        return _coffee.Cost();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create Concrete Decorators

Now, create concrete decorators that extend the CoffeeDecorator class and add functionalities:

public class Milk : CoffeeDecorator
{
    public Milk(Coffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return _coffee.GetDescription() + ", Milk";
    }

    public override double Cost()
    {
        return _coffee.Cost() + 0.50;
    }
}

public class Sugar : CoffeeDecorator
{
    public Sugar(Coffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return _coffee.GetDescription() + ", Sugar";
    }

    public override double Cost()
    {
        return _coffee.Cost() + 0.20;
    }
}

public class WhippedCream : CoffeeDecorator
{
    public WhippedCream(Coffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return _coffee.GetDescription() + ", Whipped Cream";
    }

    public override double Cost()
    {
        return _coffee.Cost() + 0.70;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Use the Decorators

Finally, use the decorators to dynamically add functionalities to the coffee objects:

class Program
{
    static void Main(string[] args)
    {
        Coffee myCoffee = new Espresso();
        Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");

        myCoffee = new Milk(myCoffee);
        Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");

        myCoffee = new Sugar(myCoffee);
        Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");

        myCoffee = new WhippedCream(myCoffee);
        Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");

        // Output:
        // Espresso $1.99
        // Espresso, Milk $2.49
        // Espresso, Milk, Sugar $2.69
        // Espresso, Milk, Sugar, Whipped Cream $3.39
    }
}
Enter fullscreen mode Exit fullscreen mode

Summary

  • Step 1: Define a Component interface or abstract class.
  • Step 2: Create Concrete Components that implement the Component interface.
  • Step 3: Create an abstract Decorator class that also implements the Component interface and holds a reference to a Component object.
  • Step 4: Create Concrete Decorators that extend the Decorator class and add functionalities.
  • Step 5: Use the decorators to dynamically add behaviors to the component.

By following these steps, you can implement the Decorator Pattern to add functionalities to objects dynamically, keeping your design flexible and adherent to the Open/Closed Principle. This approach helps in maintaining a clean and extendable codebase.

Top comments (0)