This article is a kind of debate on when to use Decorator pattern. Assumption is that the reader knows little bit about the Decorator pattern
Given the traditional example of Pizza (or Coffee) and Toppings,
We will start here with the Pizza Store
There is a pizza framework already with the Pizza company () having too many outlets which are already using the existing Pizza classes. Later they came across the idea (or requirement from many customers) of providing toppings!
One way to realize this is to extend these Pizzas for the toppings by opting for multiple inheritance. And then we will have MargheritaWithCheese, MargheritaWithPaneer, etc. It results in class explosion and the problem here is that adding one pizza or one topping will result in adding too many classes, which is cumbersome in maintaining or in managing them.
Another way to realize the addition of toppings is to go for Decorator. One of the primary design principle that the Decorator pattern solves is "Open for Extension and Closed for Modification", which is one of time tested principle when it comes to a maintainable software. So, what we cannot do here is to change the Pizza class itself. So we create derive Toppings also from the same interface as Pizzas and which will always takes a pizza as constructor parameter. As seen in the below diagram, toppings cannot be created with a pizza, which should always have a pizza when it is created.
Suppose if I have decided upfront that the pizza company decided to start with pizzas and toppings or they have decided to develop a new software for their existing product, then my design would be as below.
Now the Pizza cost will be calculated something like this (in C#)
public double Cost()
{
double finalCost = thisCost;
foreach (ToppingBase topping in toppingsList)
{
finalCost += topping.Cost();
}
return finalCost;
}
Second reason why we can go for decorator is something where the decorator is going to use the Component itself to add more responsibilities. This is something hard to grasp, if we use the pizza class itself. Here it is not necessary that the ToppingsBase is going to use the PizzaBase except for the fact that it is just going to return the cost. A very good example to explain this scenario is C# stream classes as depicted in the following picture.
Say for example, the BufferedStream, CryptoStream and GZipStream streams can be created with one another basic stream (like FileStream, NetworkStream or MemoryStream) as you can see a stream variable within those classes. But the basic streams can be created without a need for another stream. Another advantage here is these streams can be wrapped on top of another.
Here the order in which I wrap them around (the layered structure) really matters as we can get back the original stream unless we follow the reverse procedure as in the piece of code.
FileStream fileStream = new FileStream(@"D:\EmployeeInfo.xls", FileMode.Open);
Rijndael rijAlg = Rijndael.Create();
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
CryptoStream cryptedFileStream = new CryptoStream(fileStream, encryptor, CryptoStreamMode.Read);
GZipStream zippedCryptedFileStream = new GZipStream(cryptedFileStream, CompressionMode.Compress);
In the above code, we can see that CryptoStream adds additional responsibility of encryption to the FileStream. And the GZipStream compresses the encrypted stream.
I hope you are clear with the intention of decorator pattern, which is "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality" as from GoF.
Given the traditional example of Pizza (or Coffee) and Toppings,
We will start here with the Pizza Store
There is a pizza framework already with the Pizza company () having too many outlets which are already using the existing Pizza classes. Later they came across the idea (or requirement from many customers) of providing toppings!
One way to realize this is to extend these Pizzas for the toppings by opting for multiple inheritance. And then we will have MargheritaWithCheese, MargheritaWithPaneer, etc. It results in class explosion and the problem here is that adding one pizza or one topping will result in adding too many classes, which is cumbersome in maintaining or in managing them.
Another way to realize the addition of toppings is to go for Decorator. One of the primary design principle that the Decorator pattern solves is "Open for Extension and Closed for Modification", which is one of time tested principle when it comes to a maintainable software. So, what we cannot do here is to change the Pizza class itself. So we create derive Toppings also from the same interface as Pizzas and which will always takes a pizza as constructor parameter. As seen in the below diagram, toppings cannot be created with a pizza, which should always have a pizza when it is created.
Suppose if I have decided upfront that the pizza company decided to start with pizzas and toppings or they have decided to develop a new software for their existing product, then my design would be as below.
Now the Pizza cost will be calculated something like this (in C#)
public double Cost()
{
double finalCost = thisCost;
foreach (ToppingBase topping in toppingsList)
{
finalCost += topping.Cost();
}
return finalCost;
}
With this, it looks more logical to say that pizza contains toppings but not the other way.
Say for example, the BufferedStream, CryptoStream and GZipStream streams can be created with one another basic stream (like FileStream, NetworkStream or MemoryStream) as you can see a stream variable within those classes. But the basic streams can be created without a need for another stream. Another advantage here is these streams can be wrapped on top of another.
Here the order in which I wrap them around (the layered structure) really matters as we can get back the original stream unless we follow the reverse procedure as in the piece of code.
FileStream fileStream = new FileStream(@"D:\EmployeeInfo.xls", FileMode.Open);
Rijndael rijAlg = Rijndael.Create();
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
CryptoStream cryptedFileStream = new CryptoStream(fileStream, encryptor, CryptoStreamMode.Read);
GZipStream zippedCryptedFileStream = new GZipStream(cryptedFileStream, CompressionMode.Compress);
In the above code, we can see that CryptoStream adds additional responsibility of encryption to the FileStream. And the GZipStream compresses the encrypted stream.
I hope you are clear with the intention of decorator pattern, which is "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality" as from GoF.