How and Where Decorator Design Pattern

The Decorator Design Pattern is one of the behavior patterns introduced by the GOF. The Decorator Design Pattern is used during development to provide extra functionality to existing types. So the Decorator Design Pattern allows developer to satisfy the SOLID rules:

  1. Single Responsibility Principle: A class or function should do only one task and a class or function should have only one reason to change.

  2. Open Close Principle: A class or function is open for extension but closed for modification.

  3. Liskov Substitution type:  If type S is derived from type T then an object of type T can be replaced by an object of type S.

A decorator means a decor of a basic element to provide extra functionality. The following image is one of the presentations of decoration:



The image shows a gift with the basic box and the second image is decorated with gift wrap. Extra functionality is provided here is a good look at the basic gift using wrapping.

Simple Example of Design Pattern

The following image shows the class diagram of the basic Decorator Design Pattern:



IBasicService: Basic contract that needs to be implemented by derived types.

BasicServiceImplementation: Its basic/concrete implementation that is derived from interface, as it basic its provides basic functionality.

Decorator1OnBasic and Decorator2OnBasic: its decorated implementation that is derived from an interface. It's actually providing extra functionality over basic implementation.

Client: use a concrete implementation, it creates an instance of Decorate and used the functionality of it.

The code below is an implementation of the Decorator Design Pattern and the class diagram is discussed above.

  1. namespace BasicDecoratorPattern  
  2. {  
  3.     public interface IBaseService  
  4.     {  
  5.         void Print();  
  6.     }  
  7.   
  8.     public class BasicServiceImplementaion : IBaseService  
  9.     {  
  10.         public void Print()  
  11.         {  
  12.             Console.WriteLine("Basic Item");  
  13.         }  
  14.     }  
  15.   
  16.     public class Decorator1OnBasic : IBaseService  
  17.     {  
  18.         private readonly IBaseService BasicRealTimeService;  
  19.         public Decorator1OnBasic(IBaseService service)  
  20.         {  
  21.             BasicRealTimeService = service;  
  22.         }  
  23.   
  24.         public void Print()  
  25.         {  
  26.             BasicRealTimeService.Print();  
  27.             Console.WriteLine("Extra functionality from Decorator ONE");  
  28.         }  
  29.     }  
  30.   
  31.     public class Decorator2OnBasic : IBaseService  
  32.     {  
  33.         private readonly IBaseService BasicRealTimeService;  
  34.         public Decorator2OnBasic(IBaseService service)  
  35.         {  
  36.             BasicRealTimeService = service;  
  37.         }  
  38.   
  39.         public void Print()  
  40.         {  
  41.             BasicRealTimeService.Print();  
  42.             Console.WriteLine("Extra functionality from Decorator SECOND");  
  43.         }  
  44.     }  
  45.   
  46.     public class Client  
  47.     {  
  48.         public Client()  
  49.         {  
  50.             IBaseService realTimeService = new BasicServiceImplementaion();  
  51.   
  52.             IBaseService basicRealTimeServiceDecorator1 = new Decorator1OnBasic(realTimeService);  
  53.             IBaseService basicRealTimeServiceDecorator2 = new Decorator2OnBasic(realTimeService);  
  54.   
  55.             basicRealTimeServiceDecorator1.Print();  
  56.             basicRealTimeServiceDecorator2.Print();  
  57.         }  
  58.     }  

The following are points to remember in the preceding code implementation:

  1. DecoratorOnBaisc uses an IBasicService instance as input to create an instance of a decorator class.
  2. The client creates an instance of BasicServiceImplementation first and passes that instance to the decorator.
  3. The decorator uses a basic implementation instance passed as an argument to it to provide basic functionality and extra functionality by decorating it.

Output



The output shows that the decorator adds extra functionality over the basic functionality. To understand it more, the following is another realistic example of the decorator pattern.

Realistic Example of Design Pattern

The following is a class diagram of a realistic use of the design pattern. The following class diagram represents a different kind of Milkshake (Mango and Chocolate) over a basic Milkshake.



Mapping with Basic Implementation.

IMilkShake is equal to IBasicService.

MilkShake is equeal to BasicImplementation.

MangoMilshake and ChoclateMilkShake is equal to Decorator1OnBasic and Decorator2OnBasic.

Note: In this implementation MilshakeDecorator is an abstract class that is derived from an IMilkShake and the Decorator of a MilkShake is derived from this decorator class. There are some common functionalities so this class is created but it doesn't affect the actual implementation of the Decorator pattern.

  1. namespace RealWorldDecoratorPattern  
  2. {  
  3.     public interface IMilkShake  
  4.     {  
  5.         string Serve();  
  6.         int Price();  
  7.     }  
  8.   
  9.     public class MilkShake : IMilkShake  
  10.     {  
  11.         public string Serve()  
  12.         {  
  13.             return "MilkShake";  
  14.         }  
  15.   
  16.         public int Price()  
  17.         {  
  18.             return 30;  
  19.         }  
  20.     }  
  21.   
  22.     public abstract class MilkshakeDecorator : IMilkShake  
  23.     {  
  24.         public readonly IMilkShake Milkshake;  
  25.         public MilkshakeDecorator(IMilkShake milkShake)  
  26.         {  
  27.             Milkshake = milkShake;  
  28.         }  
  29.         public string Flavour { getset; }  
  30.         public int FlavourPrice { getset; }  
  31.   
  32.         public abstract string Serve();  
  33.         public abstract int Price();  
  34.   
  35.     }  
  36.   
  37.     public class MangoMilkShake : MilkshakeDecorator  
  38.     {  
  39.         public MangoMilkShake(IMilkShake milkShake)  
  40.             : base(milkShake)  
  41.         {  
  42.             this.Flavour = "Mango";  
  43.             this.FlavourPrice = 10;   
  44.         }  
  45.   
  46.         public override string Serve()  
  47.         {  
  48.             return  "Serving " + this.Flavour + " " + Milkshake.Serve();  
  49.         }  
  50.   
  51.         public override int Price()  
  52.         {  
  53.             return  this.FlavourPrice  + Milkshake.Price();  
  54.         }  
  55.     }  
  56.   
  57.     public class ChoclateMilkShake : MilkshakeDecorator  
  58.     {  
  59.         public ChoclateMilkShake(IMilkShake milkShake)  
  60.             : base(milkShake)  
  61.         {  
  62.             this.Flavour = "Choclate";  
  63.             this.FlavourPrice = 20;  
  64.         }  
  65.   
  66.         public override string Serve()  
  67.         {  
  68.             return "Serving "  + this.Flavour + " " + Milkshake.Serve();  
  69.         }  
  70.   
  71.         public override int Price()  
  72.         {  
  73.             return this.FlavourPrice + Milkshake.Price();  
  74.         }  
  75.     }  
  76.   
  77.     public class Client  
  78.     {  
  79.         public Client()  
  80.         {  
  81.             IMilkShake milkShake = new MilkShake();  
  82.   
  83.             IMilkShake mangoMilkshake = new MangoMilkShake(milkShake);  
  84.             IMilkShake choclateMilkshake = new ChoclateMilkShake(milkShake);  
  85.   
  86.             Console.WriteLine(mangoMilkshake.Serve());  
  87.             Console.WriteLine(mangoMilkshake.Price());  
  88.   
  89.             Console.WriteLine();  
  90.   
  91.             Console.WriteLine(choclateMilkshake.Serve());  
  92.             Console.WriteLine(choclateMilkshake.Price());  
  93.         }  
  94.     }  
  95.   

Output



In the preceding code the MilkShake Decorator class (Mango and Chocolate) uses the base Mikshake class. The Decorator class provides decoration on the basic Milkshake class and provides output using the basic implementation and extra functionality.

Use of Design Pattern in Application

The preceding two examples helps to explain the Decorator Design Pattern basics and realistic problems. But this section helps you to understand how to user a design pattern in applications, in other words where the developer can possibly use it in an application.

In the Decorator Design Pattern it is very helpful to provide cross-cutting concerns or aspect-oriented programming concepts like:

  1. Authentication
  2. Authorization
  3. Logging
  4. Caching
  5. Validation
  6. Exception Management

Apart from cross-cutting concerns as explained before, it can be used to decorate a class with extra added functionality, in other words it is not always true that you can use the decorator pattern just to provide cross-cutting concerns.

The following is a class diagram for caching a cross-cutting concern with the CachingDecorator.



IProvides equal to IBasicService: its contract has a GetProviderList.

Provider equal to BasicServiceImplementation: this example is a concrete implementation and is used to get a list of providers.

CacheProvider equal to DecoratorOnBasic: this is a decorator that does the task of caching fetched providers and when requested it provides cache data or if cache data is not available then it requests fresh data from the provider (basic) implementation.

  1. namespace CacheDecoratorPattern  
  2. {  
  3.     public interface IProviders  
  4.     {  
  5.         NameValueCollection GetProviderList();  
  6.     }  
  7.   
  8.     public class Providers : IProviders  
  9.     {  
  10.         public NameValueCollection GetProviderList()  
  11.         {  
  12.             NameValueCollection providerList = new NameValueCollection();  
  13.             providerList.Add("SQL""SQLProvider");  
  14.             providerList.Add("Oracle""OracleProvider");  
  15.             providerList.Add("MySQL""MyProvider");  
  16.             return providerList;  
  17.         }  
  18.     }  
  19.   
  20.     public class CacheProvider : IProviders  
  21.     {  
  22.         private readonly IProviders provider;  
  23.   
  24.         private NameValueCollection CachedProviderList;  
  25.   
  26.         public CacheProvider(IProviders provider)  
  27.         {  
  28.             this.provider = provider;  
  29.         }  
  30.   
  31.         public NameValueCollection GetProviderList()  
  32.         {  
  33.             if(CachedProviderList == null)  
  34.                 CachedProviderList = provider.GetProviderList();  
  35.   
  36.             return CachedProviderList;  
  37.         }  
  38.     }  
  39.   
  40.     public class Client  
  41.     {  
  42.         public Client()  
  43.         {  
  44.             IProviders provider = new Providers();  
  45.             CacheProvider cacheProvider = new CacheProvider(provider);  
  46.   
  47.             var providerlist = cacheProvider.GetProviderList();  
  48.         }  
  49.     }  

In the code CachProvider is a class that is a decorator over a Provider class and takes an IProvider as input. Since it is just an example, a cache value is stored in a private variable of CacheProvider, but in a real application this can be replaced by a real caching, in other words it can be a web application cache class or Enterprise application library cache block.

Decorator with Dependency injection Container

The following is just an example of code to register a decorator instance with the Microsoft Unity container.

Register Decorator

  1. var container = new UnityContainer();  
  2.  container.RegisterType(  
  3.      typeof( IProvider ),  
  4.      typeof( Provider ),  
  5.      "BasicProvider"  
  6.  );  
  7.  contract.RegisterType(  
  8.      typeof( IProvider ),  
  9.      typeof( CacheProvider ),  
  10.     new InjectionConstructor(  
  11.         new ResolvedParameter(  
  12.             typeof( IProvider ),  
  13.             "BasicProvider"  
  14.         )  
  15.     )  
  16. ); 

So once it is registered, with the Resolve method of a container one can easily get an instance of the Decorator.

var contract = container.Resolve<IProvider>();

At the end it satisfied the SOLID principle

Single Responsibility Principle: As in the example basic implementation the Provider does the task that it is responsible, for example the fetching of the data in the last example. And the decorator is responsible for doing extra functionality, like CacheProvider does the task of caching the data, not the task of getting the data.

Open Close Principle: As the rule states, here the Basic implementation Provider is closed for the modification but open for extension that is done using CachProvider that extends the functionality of basic implementation.

Liksov Subtitution Principle: As rule sates here, the basic implementation of a Provider object is replaced by the parent type IProvider interface in the Decorator constructor where the Provider object is injected by the client class.

Note:

This is my point of view regarding patterns. I welcome feedback regarding this article and also if you find something wrong in this.


Recommended Ebook

Architecting Modern Applications using Monolithic Architecture in Asp.Net Core Web API

Download Now!
Similar Articles