Quick Start On Decorator Design Pattern

Background

One of the frequently used structural patterns is the Decorator Design pattern. This pattern allows extending the existing functionality without disturbing the existing one. If you have looked at the SOLIDs principle, the decorator design pattern adheres with the Single Responsibility Principle. For more on the SOLID Principles, please go through this link - Deep Dive Into SOLID Principles.

Real-world Analogy

Let’s put technical terminology aside and understand the common terms.

If you look around the real world, there are a lot of examples that will help you understand Decorator Pattern.

  1. A normal cake base topping with different flavors and designs with a birthday/Anniversary theme.
  2. A Pizza base topping of Veg/Non-Veg.
  3. A car with a different model in Petrol/Diesel variant
  4. A car showroom vendor offers a discount on normal sessions and festival sessions.
  5. A Vanilla ice-cream cup with Nuts/Chocolate/Fruits on it.
  6. A Dosa with varieties of topping with different combinations Panner, Cheese, Corns, Potato, Peas, Peanuts, and more

And many more. If you know of something unique, please do post it in a comment box.

Example with Illustration

Let’s consider, a brand new car - Tata Nexon. There are a lot of models within the Petrol Variants like XE, XM, XM(S), XZ, XZ+, and XZ+(S).

Here the base model of the Petrol variant has been extended to many sub-models by adding new functionality and features to the existing base model of the car. The features like Power window, Halogen lamp, Sunroof, Auto climate sensor, rear wiper, Alloy wheels, and many more, these combinations of features and some additional functionality turn into a different model. This illustration represents an opening for extending the functionality without disturbing the existing one.

When can we use the Decorator design pattern?

  1. Extend the existing functionality without disturbing the existing one.
  2. When the system is legacy and required to add new functionality.

Pros

  1. Flexable in design with alternative subclasses
  2. Adhering to Open-Close Principle
  3. Add and remove the behaviors at runtime
  4. Combine several behaviors by wrapping an object into multiple decorators.

UML Representation

Code example - Offers Defined by Car Showroom

In the above UML diagram, let’s consider Car showroom had introduced many offers and schemes to provide a benefit to the customers. Here we will design a system to implement by Decorator design pattern and will see how the class representations.

Let’s define a ‘Component’ as ‘ICar’ interface, that has the signature (GetExShowRoomPrice()) to get the current Ex-Showroom price of the car. Now will define the ‘Concrete component’ as ‘Tata’ and ‘Honda’ Class. That has the implementation of the current Ex-Showroom price for each concrete component.

Now, here is the very important things in class representation to deal with the Decorator pattern. Define the ‘Decorator’ as ‘OfferDecorator’ Abstract class and have the signature(GetOfferPrice()) to get the current offers for the car. Define the ‘ConcreteDecorator’ for different kinds of offers like Diwali offer, Staff offer, and Year-End offer.

  1. ‘DiwaliOffer’ is a concrete decorator class implementing ‘OfferDecorator’.
  2. ‘StaffOffer’ is a concrete decorator class implementing ‘DiwaliOffer’.
  3. YearEndOffer is a concrete decorator class implementing ‘OfferDecorator’

Let’s define ‘ICar’ interface as a Component.

namespace DP.DecoratorPattern.Component  
{  
    public interface ICar  
    {  
        string Name { get; set; }  
        string YearOfModel { get; set; }  
  
        double GetExShowRoomPrice();  
    }  
}  

Define Concrete component class ‘Tata’ and return current Ex-Showroom price as 20,00,000.

namespace DP.DecoratorPattern.ConcrateComponent  
{  
    using DP.DecoratorPattern.Component;  
  
    public sealed class Tata : ICar  
    {  
        public Tata(string carName, string yearOfModel)  
        {  
            Name = carName;  
            YearOfModel = yearOfModel;  
        }  
  
        public string Name { get; set; }  
  
        public string YearOfModel { get; set; }  
  
        public double GetExShowRoomPrice()  
        {  
            return 2000000;  
        }  
    }  
}  

Define Concrete component class ‘Honda’ and return current Ex-Showroom price as 10,00,000.

namespace DP.DecoratorPattern.ConcrateComponent  
{  
    using DP.DecoratorPattern.Component;  
  
    public sealed class Honda : ICar  
    {  
        public Honda(string carName, string yearOfModel)  
        {  
            Name = carName;  
            YearOfModel = yearOfModel;  
        }  
  
        public string Name { get; set; }  
  
        public string YearOfModel { get; set; }  
  
        public double GetExShowRoomPrice()  
        {  
            return 1000000;  
        }  
    }  
}  

Define Decorator class ‘OfferDecorator’,

namespace DP.DecoratorPattern.Decorator  
{  
    using DP.DecoratorPattern.Component;  
  
    public abstract class OfferDecorator : ICar  
    {  
        private ICar _car;  
        public OfferDecorator(ICar car)  
        {  
            _car = car;  
        }  
  
        public string Name { get { return _car.Name; } set { _car.Name = value; } }  
        public string YearOfModel { get { return _car.YearOfModel; } set { _car.YearOfModel = value; } }  
  
        public double GetExShowRoomPrice()  
        {  
            return _car.GetExShowRoomPrice();  
        }  
  
        public abstract double GetOfferPrice();  
    }  
}  

‘DiwaliOffer’ is a concrete decorator class implementing ‘OfferDecorator’ and returns with 30% of discount on Ex-Showroom price.

namespace DP.DecoratorPattern.ConcrateDecorator  
{  
    using DP.DecoratorPattern.Component;  
    using DP.DecoratorPattern.Decorator;  
  
    public class DiwaliOffer : OfferDecorator  
    {  
        public DiwaliOffer(ICar car) : base(car)  
        {  
        }  
  
        public override double GetOfferPrice()  
        {  
            return base.GetExShowRoomPrice() - (.30 * base.GetExShowRoomPrice());  
        }  
    }  
}  

‘StaffOffer’ is a concrete class implementing ‘DiwaliOffer’ and returns with 30% + 10% of discount of Ex-Showroom price. If these kinds of offers are provided by Car showroom vendor, the customer will be a King, but this will never happen in reality 😊

namespace DP.DecoratorPattern.ConcrateDecorator  
{  
    using DP.DecoratorPattern.Component;  
  
    public class StaffOffer : DiwaliOffer  
    {  
        public StaffOffer(ICar car) : base(car)  
        {  
        }  
  
        public override double GetOfferPrice()  
        {  
            return base.GetOfferPrice() - (.10 * base.GetExShowRoomPrice());  
        }  
    }  
}  

YearEndOffer is a concrete class implementing ‘OfferDecorator’ and returns with 20% of discount of Ex-Showroom price.

namespace DP.DecoratorPattern.ConcrateDecorator  
{  
    using DP.DecoratorPattern.Component;  
    using DP.DecoratorPattern.Decorator;  
  
    public class YearEndOffer : OfferDecorator  
    {  
        public YearEndOffer(ICar car) : base(car)  
        {  
        }  
  
        public override double GetOfferPrice()  
        {  
            return base.GetExShowRoomPrice() - (.20 * base.GetExShowRoomPrice());  
        }  
    }  
}  

Program Execution

  • Create an instance of Component as Tata Car – Hexa
  • Create an instance of Offer Decorator with DiwaliOffer
  • Get the ex-showroom price and office price

In case any additional top on DiwaliOffer, apply StaffOffer over to DiwaliOffer,

namespace DP.DecoratorPattern  
{  
    using DP.DecoratorPattern.Component;  
    using DP.DecoratorPattern.ConcrateComponent;  
    using DP.DecoratorPattern.ConcrateDecorator;  
    using DP.DecoratorPattern.Decorator;  
    using System;  
  
    public class Program  
    {  
        public static void Main(string[] args)  
        {  
            ICar car = new Tata("Hexa", "2019");  
  
            OfferDecorator offerDecorator = new DiwaliOffer(car);  
            Console.WriteLine("Ex-showroom price : " + offerDecorator.GetExShowRoomPrice());  
  
            Console.WriteLine("Ex-showroom price after Diwali offer: " + offerDecorator.GetOfferPrice());  
  
            offerDecorator = new StaffOffer(offerDecorator);  
            Console.WriteLine("Ex-showroom price after Staff offer: " + offerDecorator.GetOfferPrice());  
  
            Console.ReadLine();  
        }  
    }  
}  

Output

Here as the output shows, the base Ex-showroom price of Tata Hexa car is 20,00,000/-

Diwali offer gives you 30% off discount, that is 14,00,000/- Ex-showroom price

With the addition of some offer from Staff side with 10% of discount, that is 12,00,000/- Ex-showroom price.

I hope now you have got an idea of how the Decorator pattern helps to solve the real case scenario and it is very fun to play with it. I have attached a sample code sample and you can add any kind of decorator to accommodate your needs.

I hope this article helps you to understand the simple approach. Thanks for reading the article.