Decorator Design Pattern

Decorator pattern is one of the most important and widely used design pattern. It helps the user to add new functionalities without altering the existing code structure.

Lets review the basic class diagram for this pattern decorator_pattern.png

Why to use it

  • It provides a very good design approach to add more features to our existing functionalities.
  • Developers dont need to modify existing classes and avoid violating open closed principle.
  • Developers can add new features by adding new decorator classes which enhance the behaviour of already existing component classes.

When to use it

You are working on new project for yourself/employer or someone else. You developed a new feature and released it in production. You made sure that each feature works well and tested them properly. But somehow requirements changed after some time and you came back to existing classes to enhance them with new features. But wait! Changing existing classes will now violate one of the SOLID design, open-closed principle. How do you add new feature without modifying existing classes. Here decorator pattern will comes to rescue. Decorator pattern helps you in decorating your existing classes with new features.

Lets go through an example of making a tea

Defining Requirements

We need to make a simple class which will output cost of making a tea and description of tea. Sounds Pretty easy. Lets write the code and define the interface.

Defining an interface

  public interface ITea {
    double GetCost();
    string GetDescription();
  }

Implemeting the above interface

Lets make a Tea class implementing this interface

  public class Tea : ITea {

    public double GetCost() {
      return 3.0;
    }

    public string GetDescription() {
      return "Simple Tea";
    }
  }

When we make an object of the Tea class and call the GetCost() and GetDescription() it works fine. Cool. We made a new feature.

Now after some time business requirement changed and business want to charge additional amount to consumer for extra milk or extra sugar in tea. What can be done? Obviously we can modify the above class and pass some parameters and produce response using if-else statements.

Should we do it? The clear answer is “NO”. Why? It violates open-closed principle. In real production system, changing already tested features and classes, can cause regression and it will unnecessarily put lot of pressure on our testers. So, What’s the solution? Lets introduce another abstract class called TeaDecorator which inherits from the same component as our already existing classes.

  public abstract class TeaDecorator : ITea {

    private readonly ITea tea;
    public TeaDecorator(ITea tea) {
      this.tea = tea;
    }

    public virtual double GetCost() {
      var cost = tea.GetCost();
      return cost;
    }

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

The above class TeaDecorator implements ITea interface similar to our existing Tea class. It also contains a reference of ITea in the same class so its subclasses can hold reference of the class which need to be decorated/enhanced.

As mentioned above, our requirements have now changed and business needs to charge money for extra milk and extra sugar in addition to existing cost. So lets introduce two new classes called extra milk and extra sugar.

Please note how these new concrete decorator classes inherits from the above abstract TeaDecorator class and accepts a type of ITea instance in their constructor.

Below class charges 0.75 cents extra for additional milk in addition to existing tea cost

  public class WithExtraMilk : TeaDecorator {

    public WithExtraMilk(ITea t) : base(t) {
    }

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

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

Below class charges 0.50 cents extra for additional sugar in addition to existing tea cost

  public class WithExtraSugar : TeaDecorator {

    public WithExtraSugar(ITea t) : base(t) {
    }

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

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

Wow! Our new implementation is almost ready with new features. We have implemented new features without violating open closed principle. So the above decorator classes provided a wrapper around already existing classes and helps us in avoiding to change existing structure altogether.

Finally, our client can consume these new classes with the below code

    ITea tea = new Tea();
    // Make a simple tea
    Console.WriteLine(Environment.NewLine);
    Console.WriteLine(tea.GetDescription() + " is " + tea.GetCost());

    // Decorate the tea with extra milk
    tea = new WithExtraMilk(tea);
    Console.WriteLine(tea.GetDescription() + " is " + tea.GetCost());

    tea = new WithExtraSugar(tea);
    Console.WriteLine(tea.GetDescription() + " is " + tea.GetCost());

As you can see, we created a tea object and passed it into our new decorator classes to provide decoration to our already existing class objects.

Hope this article helped you to understand decorator pattern clearly.

You can find the whole code for decorator pattern here

Published 9 May 2020

Ramblings of a Software Engineer
Deepak Sharma on Twitter