Decorator Pattern in C#

Decorator Pattern



The Decorator Pattern provides a flexible alternative to sub classing for extending functionality dynamically.

The idea of the Decorator Pattern is to wrap an existing class, add other functionality to it, then expose the same interface to the outside world. Because of this our decorator exactly looks like the original class to the people who are using it.

It is used to extend or alter the functionality at runtime. It does this by wrapping them in an object of the decorator class without modifying the original object. So it can be called a wrapper pattern.

Advantages of Decorator Pattern

  1. Adds functionality to existing objects dynamically
  2. Alternative to sub classing
  3. Flexible design
  4. Supports Open Closed Principle

When to use Decorator Pattern

  1. Legacy System
  2. Contorls
  3. Sealed Classes

The following class diagram shows us the Decorator Pattern's design:


  • Component: It defines the interface of the actual object that needs functionality to be added dynamically to the ConcreteComponents.
  • ConcreteComponent: The actual object in which the functionalities could be added dynamically.
  • Decorator: This defines the interface for all the dynamic functionalities that can be added to the ConcreteComponent.
  • ConcreteDecorator: All the functionalities that can be added to the ConcreteComponent. Each needed functionality will be one ConcreteDecorator class.

A decorated class is a base class of concrete decorator classes and it inherits from the base component class. So the decorators can be used in the place of concrete components.

Let's start learning the Decorator Pattern with one real time problem. Suppose we need to build an application for a car show room that sells cars along with car accessories. Economy, Deluxe and Luxury are the car categories that come with the optional accessories packages Basic, Advanced and Sport. So the application must provide the car cost adding the cost of accessory package if the user chooses any of the preceding packages.

Okay. Let's start designing our application with the ICar interface that will be implemented by Economy, Deluxe and Luxury Car category classes.

  1. /// <summary>   
  2. /// Car Base component   
  3. /// </summary>   
  4. public interface ICar   
  5. {   
  6.    string GetDescription();   
  7.    double GetCost();   
  8. }   
  9.   
  10. /// <summary>   
  11. /// Concrete Car   
  12. /// </summary>   
  13. public class EconomyCar : ICar   
  14. {   
  15.    public string GetDescription()   
  16.    {   
  17.       return "Economy Car";   
  18.    }   
  19.   
  20.    public double GetCost()   
  21.    {   
  22.       return 450000.0;   
  23.    }   
  24. }   
  25.   
  26. /// <summary>   
  27. /// Concrete Car   
  28. /// </summary>   
  29. public class DeluxCar : ICar   
  30. {   
  31.    public string GetDescription()   
  32.    {   
  33.       return "Delux Car";   
  34.    }   
  35.   
  36.    public double GetCost()   
  37.    {   
  38.       return 750000.0;   
  39.    }   
  40. }   
  41.   
  42. /// <summary>   
  43. /// Concrete Car   
  44. /// </summary>   
  45. public class LuxuryCar : ICar   
  46. {   
  47.    public string GetDescription()   
  48.    {   
  49.       return "Luxury Car";   
  50.    }   
  51.   
  52.    public double GetCost()   
  53.    {   
  54.       return 1000000.0;   
  55.    }   
  56. }   
We are good up to this. But what about the optional accessory packages? How we will design them? Can we design them using subclassing like the following?
  1. public class EconomyCarWithBasicAccessories: ICar{   
  2. ..   
  3. ..   
  4. }   
  5. public class DeluxCarWithBasicAccessories: ICar{   
  6. ..   
  7. ..   
  8. }   
  9. public class LuxuryCarWithBasicAccessories: ICar{   
  10. ..   
  11. ..   
  12. }   
  13.   
  14. ..   
  15. ..   
  16. ..   
  17. ..   
  18. public class EconomyCarWithSportAccessories: ICar{   
  19. ..   
  20. ..   
  21. }   
  22. public class DeluxCarWitSportAccessories: ICar{   
  23. ..   
  24. ..   
  25. }   
  26. public class LuxuryCarWithSportAccessories: ICar{   
  27. ..   
  28. ..   
  29. }   
Yes we can build our classes like the above. But the class count keeps on increasing by 4 whenever we introduce either one car category or one accessory package into our system. Because of this application size will become huge and after some time it will be difficult to manage.

To avoid this we can get help from our hero the Decorator Pattern. As I already said, this pattern is used to attach or add extra functionality to the object at run time without altering the class structure. That means there is no need to create extra sub classes whenever we introduce a new car category or new accessory packages.

Let's build our system using the Decorator Pattern.
  1. /// <summary>   
  2. /// Car Base component   
  3. /// </summary>   
  4. public interface ICar   
  5. {   
  6.    string GetDescription();   
  7.    double GetCost();   
  8. }   
  9.   
  10. /// <summary>   
  11. /// Concrete Car   
  12. /// </summary>   
  13. public class EconomyCar : ICar   
  14. {   
  15.    public string GetDescription()   
  16.    {   
  17.       return "Economy Car";   
  18.    }   
  19.   
  20.    public double GetCost()   
  21.    {   
  22.       return 450000.0;   
  23.    }   
  24. }   
  25.   
  26. /// <summary>   
  27. /// Concrete Car   
  28. /// </summary>   
  29. public class DeluxCar : ICar   
  30. {   
  31.    public string GetDescription()   
  32.    {   
  33.       return "Delux Car";   
  34.    }   
  35.   
  36.    public double GetCost()   
  37.    {   
  38.       return 750000.0;   
  39.    }   
  40. }   
  41.   
  42. /// <summary>   
  43. /// Concrete Car   
  44. /// </summary>   
  45. public class LuxuryCar : ICar   
  46. {   
  47.    public string GetDescription()   
  48.    {   
  49.       return "Luxury Car";   
  50.    }   
  51.   
  52.    public double GetCost()   
  53.    {   
  54.       return 1000000.0;   
  55.    }   
  56. }   
  57.   
  58. /// <summary>   
  59. /// Abstract Decorator   
  60. /// </summary>   
  61. public abstract class CarAccessoriesDecorator : ICar   
  62. {   
  63.   
  64.    private ICar _car;   
  65.   
  66.    public CarAccessoriesDecorator(ICar aCar)   
  67.    {   
  68.       this._car = aCar;   
  69.    }   
  70.    public virtual string GetDescription()   
  71.    {   
  72.       return this._car.GetDescription();   
  73.    }   
  74.   
  75.    public virtual double GetCost()   
  76.    {   
  77.       return this._car.GetCost();   
  78.    }   
  79. }   
  80.   
  81. /// <summary>   
  82. /// Concrete Decorator   
  83. /// </summary>   
  84. public class BasicAccessories : CarAccessoriesDecorator   
  85. {   
  86.    public BasicAccessories(ICar aCar)   
  87.    : base(aCar)   
  88.    {   
  89.   
  90.    }   
  91.   
  92.    public override string GetDescription()   
  93.    {   
  94.       return base.GetDescription() + ",Basic Accessories Package";   
  95.   
  96.    }   
  97.   
  98.    public override double GetCost()   
  99.    {   
  100.       return base.GetCost() + 2000.0;   
  101.    }   
  102. }   
  103.   
  104. /// <summary>   
  105. /// Concrete Decorator   
  106. /// </summary>   
  107. public class AdvancedAccessories : CarAccessoriesDecorator   
  108. {   
  109.    public AdvancedAccessories(ICar aCar)   
  110.    : base(aCar)   
  111.    {   
  112.   
  113.    }   
  114.   
  115.    public override string GetDescription()   
  116.    {   
  117.       return base.GetDescription() + ",Advanced Accessories Package";   
  118.    }   
  119.   
  120.    public override double GetCost()   
  121.    {   
  122.       return base.GetCost() + 10000.0;   
  123.    }   
  124. }   
  125.   
  126. /// <summary>   
  127. /// Concrete Decorator   
  128. /// </summary>   
  129. public class SportsAccessories : CarAccessoriesDecorator   
  130. {   
  131.    public SportsAccessories(ICar aCar)   
  132.    : base(aCar)   
  133.    {   
  134.   
  135.    }   
  136.   
  137.    public override string GetDescription()   
  138.    {   
  139.       return base.GetDescription() + ",Sports Accessories Package";   
  140.    }   
  141.   
  142.    public override double GetCost()   
  143.    {   
  144.       return base.GetCost() + 15000.0;   
  145.    }   
  146. }   
The following is the class diagram of the above design.



Here we successfully avoided the sub classing issue. Whenever we need to add a new car category we only must add one class for it like the following.
  1. public class SuperLuxury : ICar   
  2. {   
  3.    public string GetDescription()   
  4.    {   
  5.       return "Super Luxury Car";   
  6.    }   
  7.   
  8.    public double GetCost()   
  9.    {   
  10.       return 1500000;   
  11.    }   
  12. }   
And we need to add only one class for a new accessory package for our system.
  1. pubic classs ExtremeSportAccessories : CarAccessoryDecorator   
  2. {   
  3.    public ExtremeSportAccessories(ICar aCar)   
  4.    :base(aCar)   
  5.    {   
  6.    }   
  7.    public override string GetDescription()   
  8.    {   
  9.       return base.GetDescription() +",Extreme Sport Accessories";   
  10.    }   
  11.    public override double GetCost()   
  12.    {   
  13.       return base.GetCost() + 25000;   
  14.    }   
  15. }   
Nice design, right? But how to extend the functionality of an instance at run time? Good question. The following code will explain that.
  1. static void Main(string[] args)   
  2. {   
  3.    //Create EcomomyCar instance.   
  4.    ICar objCar = new EconomyCar();   
  5.   
  6.    //Wrp EconomyCar instancw with BasicAccessories.   
  7.    CarAccessoriesDecorator objAccessoriesDecorator = new BasicAccessories(objCar);   
  8.   
  9.    //Wrap EconomyCar instance with AdvancedAccessories instance.   
  10.    objAccessoriesDecorator = new AdvancedAccessories(objAccessoriesDecorator);   
  11.   
  12.    Console.Write("Car Detials: " + objAccessoriesDecorator.GetDescription());   
  13.    Console.WriteLine("\n\n");   
  14.    Console.Write("Total Price: " + objAccessoriesDecorator.GetCost());   
  15.   
  16.    Console.Read();   
  17. }   
In the above we created an instance of EcomomyCar and we added accessories functionality, one by one, by wrapping the instance with the accessories instances.

I hope you enjoyed this article. Thank you.

 

Up Next
    Ebook Download
    View all
    Learn
    View all