DEV Community

Theodore Karropoulos
Theodore Karropoulos

Posted on

Decorator Design Pattern

The decorator pattern is a structural design pattern that give us the ability to enhance the functionality of existing objects. This is done by surrounding the objects with a special kind of class known as a decorator class which can add new behavior to the object at runtime.

Problem

Consider a scenario where you have to create a software application for a coffee shop. The application allows the user to order coffee drinks and customize them with various toppings. One way to solve this problem is to use simple condition statements to check what toppings the customer wants. This approach is simple and easy to understand but as the number of toppings grows the conditional statements can become complex and difficult to maintain.

Solution

The decorator pattern is an excellent way to tackle such a problem because we can add new functionality without modifying the existing code, making it a more flexible solution as the number of toppings grow.

Decorator pattern consists of the following parts:

  • Component: This is the interface that defines the methods that the objects being decorated should implement.
  • Concrete Component: This is the class that implements the implements the Component interface
  • Decorator: This is an abstract class that implements the Component interface. It also holds a reference to an object of the Component type.
  • Concrete Decorator: This is the class that extends the Decorator class and adds new behavior to the objects.
  • Client: Can wrap components in multiple layers of decorators, this is the class that uses the decorator pattern.

Coffee shop code example

// This is our component interface
public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}
Enter fullscreen mode Exit fullscreen mode

Next step is to create a concrete class that implements our component interface. Lets call our class Espresso, since we as developers like to drink a lot if it 😄 :

// This is our concrete component class
public sealed class Espresso : ICoffee
{
    public string GetDescription()
    {
        return "Espresso";
    }

    public decimal GetCost()
    {
        return 1.99m;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then, we can create a decorator for example ToppingDecorator that implements the ICoffee interface and contains a reference to an ICoffee object. Decorators can be abstract class or interface:

// This is our decorator abstract class
public abstract class ToppingDecorator : ICoffee
{
    protected readonly ICoffee Coffee;

    protected ToppingDecorator(ICoffee coffee)
    {
        Coffee = coffee;
    }

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

    public virtual decimal GetCost()
    {
        return Coffee.GetCost();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can finally create a concrete decorator class that will add behavior to our espresso and give some extra flavor! On our example we will create two decorator classes one named WhippedCream and the other ChocolateSyrup:

// This is our first concrete decorator class
public sealed class WhippedCream : ToppingDecorator
{
    public WhippedCream(ICoffee coffee) : base(coffee)
    {
    }

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

    public override decimal GetCost()
    {
        return Coffee.GetCost() + 0.5m;
    }
}

// This is our second concrete decorator class
public sealed class ChocolateSyrup : ToppingDecorator
{
    public ChocolateSyrup(ICoffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return Coffee.GetDescription() + ", Chocolate Syrup";
    }

    public override decimal GetCost()
    {
        return Coffee.GetCost() + 0.75m;
    }
}
Enter fullscreen mode Exit fullscreen mode

Last but not least we can use the decorator classes to create and customize coffee drinks in our client:

// This is our client
ICoffee espresso = new Espresso();
Console.WriteLine(espresso.GetDescription() +
 ": $" + espresso.GetCost());

 // Add whipped cream and chocolate syrup to the espresso
 ICoffee espressoWithToppings = new WhippedCream(
new ChocolateSyrup(espresso));
 console.WriteLine(espressoWithToppings.GetDescription()
 + ": $" + espressoWithToppings.GetCost());
Enter fullscreen mode Exit fullscreen mode

The output of the above should be the following:

Espresso: $1,99
Espresso, Chocolate Syrup, Whipped Cream: $3,24
Enter fullscreen mode Exit fullscreen mode

When to use the decorator pattern

We should use decorator pattern when we want to add new behavior to an individual object or a group of related objects without affecting the behavior of other objects of the same class.

Pros and cons of using the decorator pattern

✔️ Allow us to add new behavior to existing objects dynamically
✔️ Make it easy to create different combinations of behavior
✔️ Making it easier to change the objects without affecting the client code
❌ Increases the complexity of the code
❌ It may make the code less straightforward to read, as it adds an additional level of complexity.

Top comments (1)

Collapse
 
teaganga profile image
teaganga

Nice example. I implemented decorator pattern in JavaScript, trying to make use of the language features, so instead of a class i created it as a function.