Introduction
Decorator design pattern is one of the behaviour patterns introduced by GOF. Decorator design pattern is used during development to provide extra functionality to existing Types. So Decorator Design pattern allows developer to achieve rule of SOLID rules.
- Single Responsibility Principle – Class/Function should do only one task or Class/Function should have only one reason to change.
- Open Close Principle – Class/Function is open for extension but close for modification.
- Liskov Substitution type – If type S is derived from Type T then object of Type T can be replaced by object of Type S.
Decorator means decor basic element to achieve extra functionality. The below image is one of the presentations of decoration.
|
|
Basic Gift
| Gift Wrap
|
The image shows gift with the basic box and other image is decoration on gift with good wrapping. Extra functionality achieved over here is good look on the basic gift with the help of wrapping decoration.
Simple Example of Design Pattern
The below image shows the class diagram of basic decorator design pattern.
IBasicService
- Basic contract that needs to be implemented by derived types. BasicServiceImplementation
– Its basic/concrete implementation which is derived from interface, as basic it provides basic functionality. Decorator1OnBasic
& Decorator2OnBasic
– Its decorated implementation which is derived from interface. It’s actually providing extra functionality over basic implementation. Client
– Makes use of the concrete implementation, it creates instance of Decorate
and use functionality of it.
The code below is implementation of the Decorator design pattern and Class diagram discussed above.
namespace BasicDecoratorPattern
{
public interface IBaseService
{
void Print();
}
public class BasicServiceImplementaion : IBaseService
{
public void Print()
{
Console.WriteLine("Basic Item");
}
}
public class Decorator1OnBasic : IBaseService
{
private readonly IBaseService BasicRealTimeService;
public Decorator1OnBasic(IBaseService service)
{
BasicRealTimeService = service;
}
public void Print()
{
BasicRealTimeService.Print();
Console.WriteLine("Extra functionality from Decorator ONE");
}
}
public class Decorator2OnBasic : IBaseService
{
private readonly IBaseService BasicRealTimeService;
public Decorator2OnBasic(IBaseService service)
{
BasicRealTimeService = service;
}
public void Print()
{
BasicRealTimeService.Print();
Console.WriteLine("Extra functionality from Decorator SECOND");
}
}
public class Client
{
public Client()
{
IBaseService realTimeService = new BasicServiceImplementaion();
IBaseService basicRealTimeServiceDecorator1 = new Decorator1OnBasic(realTimeService);
IBaseService basicRealTimeServiceDecorator2 = new Decorator2OnBasic(realTimeService);
basicRealTimeServiceDecorator1.Print();
basicRealTimeServiceDecorator2.Print();
}
}
}
Points to remember in the above code implementation are listed below:
DecoratorOnBasic
takes IBasicService
instance as input to create instance of decorator
class. - Client creates instance of
BasicServiceImplementation
first and passes that instance to the decorator
. Decorator
makes use of the basic implementation instance passed as argument to it to achieve basic functionality and extra functionality by decorating it.
Output
Output shows that decorator adds extra functionality over the basic functionality. To understand more, below is one more example of decorator pattern in the real world.
Real-World Example of Design Pattern
Below is a class diagram of Real-World design pattern. The below class diagram represents different kind, i.e., different kind of the Milkshake
(Mango
& Chocolate
) over basic Milkshake
.
Mapping with Basic Implementation
IMilkShake
is equals to IBasicService
MilkShake
is equal to BasicImplementation
MangoMilkshake
& ChoclateMilkShake
is equal to Decorator1OnBasic
& Decorator2OnBasic
Note in this implementation, MilkshakeDecorator
is an abstract
class which is derived from the IMilkShake
and Decorator
of the MilkShake
is derived from this decorator
class. There is some common functionality so this class is created but it doesn’t affect the actual implementation of Decorator pattern.
namespace RealWorldDecoratorPattern
{
public interface IMilkShake
{
string Serve();
int Price();
}
public class MilkShake : IMilkShake
{
public string Serve()
{
return "MilkShake";
}
public int Price()
{
return 30;
}
}
public abstract class MilkshakeDecorator : IMilkShake
{
public readonly IMilkShake Milkshake;
public MilkshakeDecorator(IMilkShake milkShake)
{
Milkshake = milkShake;
}
public string Flavour { get; set; }
public int FlavourPrice { get; set; }
public abstract string Serve();
public abstract int Price();
}
public class MangoMilkShake : MilkshakeDecorator
{
public MangoMilkShake(IMilkShake milkShake)
: base(milkShake)
{
this.Flavour = "Mango";
this.FlavourPrice = 10;
}
public override string Serve()
{
return "Serving " + this.Flavour + " " + Milkshake.Serve();
}
public override int Price()
{
return this.FlavourPrice + Milkshake.Price();
}
}
public class ChoclateMilkShake : MilkshakeDecorator
{
public ChoclateMilkShake(IMilkShake milkShake)
: base(milkShake)
{
this.Flavour = "Choclate";
this.FlavourPrice = 20;
}
public override string Serve()
{
return "Serving " + this.Flavour + " " + Milkshake.Serve();
}
public override int Price()
{
return this.FlavourPrice + Milkshake.Price();
}
}
public class Client
{
public Client()
{
IMilkShake milkShake = new MilkShake();
IMilkShake mangoMilkshake = new MangoMilkShake(milkShake);
IMilkShake choclateMilkshake = new ChoclateMilkShake(milkShake);
Console.WriteLine(mangoMilkshake.Serve());
Console.WriteLine(mangoMilkshake.Price());
Console.WriteLine();
Console.WriteLine(choclateMilkshake.Serve());
Console.WriteLine(choclateMilkshake.Price());
}
}
}
Output
In the above code, MilkShake Decorator
class (Mango
and Chocolate
) make use of base Mikshake
class. Decorator
class provides decoration on the Basic Milkshake
class and provides output by using basic implement and extra functionality.
Use of Design Pattern in Application
The above two examples help to understand Decorator design pattern Basic
and RealWorld
problem. But this section is to help you to understand how to user Design pattern in Application, i.e., where developer can possibly use it in Application.
Decorator design pattern is very helpful to achieve cross cutting concern/Aspect oriented programming concepts like:
- Authentication
- Authorization
- Logging
- Caching
- Validation
- Exception Management
Apart from Cross cutting concern as explained before, it can be used to decorate class with extra added functionality, i.e., it is not always true that you can use decorator pattern just to achieve cross cutting concern.
Below is the Class diagram of the achieving Caching Cross Cutting Concern with the CachingDecorator
.
IProvides
is equal to IBasicService
– for this example its contract which is having GetProviderList
. - Provider is equal to
BasicServiceImplementation
– for this example is concrete implementation and used to get list of providers. CacheProvider
is equal to DecoratorOnBasic
– for this example, this is decorator which 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 Provider (basic) implementation.
namespace CacheDecoratorPattern
{
public interface IProviders
{
NameValueCollection GetProviderList();
}
public class Providers : IProviders
{
public NameValueCollection GetProviderList()
{
NameValueCollection providerList = new NameValueCollection();
providerList.Add("SQL", "SQLProvider");
providerList.Add("Oracle", "OracleProvider");
providerList.Add("MySQL", "MyProvider");
return providerList;
}
}
public class CacheProvider : IProviders
{
private readonly IProviders provider;
private NameValueCollection CachedProviderList;
public CacheProvider(IProviders provider)
{
this.provider = provider;
}
public NameValueCollection GetProviderList()
{
if(CachedProviderList == null)
CachedProviderList = provider.GetProviderList();
return CachedProviderList;
}
}
public class Client
{
public Client()
{
IProviders provider = new Providers();
CacheProvider cacheProvider = new CacheProvider(provider);
var providerlist = cacheProvider.GetProviderList();
}
}
}
In code CacheProvider
is class which is decorator over Provider
class and take IProvider
as input to it. As it is just an example right now cache value is stored in private
variable of CacheProvider
but in real application, this can be replaced by real caching, i.e., it can be web application cache class or Enterprise application library cache block.
Decorator with Dependency Injection Container
Below is just example code to register decorator instance with the Microsoft Unity container.
Register Decorator
var container = new UnityContainer();
container.RegisterType(
typeof( IProvider ),
typeof( Provider ),
"BasicProvider"
);
contract.RegisterType(
typeof( IProvider ),
typeof( CacheProvider ),
new InjectionConstructor(
new ResolvedParameter(
typeof( IProvider ),
"BasicProvider"
)
)
);
So once it gets registered with Resolve
method of container, one can easily get an instance of the Decorator
.
var contract = container.Resolve<IProvider>();
At the End Achieved SOLID principle:
- Single Responsibility Principle – As in the example, Basic implementation Provider does the task which it is responsible for example – fetching data in last example. And the decorator is responsible for doing extra functionality like
CacheProvider
does the task for caching data not task of getting data. - Open Close Principle - As rule states here, Basic implementation Provider is close for the modification but open for extension that is achieved through
CacheProvider
which extends functionality of basic implementation. - Liksov Substitution Principle – As the rule states here, Basic implementation Provider object replaced by the parent type
IProvider
interface in Decorator
constructor where Provider
object is injected by client class.
Note
This is my point of view regarding pattern. Please provide your feedback regarding it and also provide feedback if you find something wrong in this post.