In this article, we are going to talk about another structural C# design pattern, the Decorator Design Pattern. We are going to learn, how to implement this pattern in our project and what we can get by doing that.
The source code is available at the Decorator Design Pattern – Source Code.
For the main page of this series check out C# Design Patterns.
About the Decorator Design Pattern
A Decorator is a structural design pattern that allows us to extend the behavior of objects by placing these objects into a special wrapper class. The Decorator design pattern is quite popular in C# due to the fact that it helps us dynamically add behaviors to the wrapped objects.
The structure of this pattern consists of a Component class and a Concrete Component class from one part and a Decorator class and a Concrete Decorator class on the other side. The Concrete Decorator class is going to add additional behavior to our Concrete Component.
So, when do we use this pattern?
Well, we should use this pattern when we have a need to add additional behavior to our objects. Furthermore, we should use it when it is too complicated to use inheritance or when it doesn’t make sense at all (too many inherit layers, urge to modify existing inheritance hierarchy by adding some additional layers, etc.).
We are going to see how all these elements work together inside the Decorator design pattern through the practical example, which is going to make this pattern easier to comprehend.
Decorator Design Pattern Implementation
Let’s imagine that we have a simple application to calculate the total order price in our shop. But also, we have some additional requests. If a buyer orders our products in a preorder period we are going to give a 10 percent discount. So, let’s start first with our Component class:
public abstract class OrderBase { protected List<Product> products = new List<Product> { new Product {Name = "Phone", Price = 587}, new Product {Name = "Tablet", Price = 800}, new Product {Name = "PC", Price = 1200} }; public abstract double CalculateTotalOrderPrice(); }
The Component class contains functionality that will be shared with other Concrete Component classes. Having that in mind, let’s create the Concrete Components:
public class RegularOrder : OrderBase { public override double CalculateTotalOrderPrice() { Console.WriteLine("Calculating the total price of a regular order"); return products.Sum(x => x.Price); } }
public class Preorder : OrderBase { public override double CalculateTotalOrderPrice() { Console.WriteLine("Calculating the total price of a preorder."); return products.Sum(x => x.Price) * 0.9; } }
This code is pretty clean and easy to understand. We just override our abstract method CalculateOrderPrice
and calculate the total price. So, right now, we can start using these concrete components:
class Program { static void Main(string[] args) { var regularOrder = new RegularOrder(); Console.WriteLine(regularOrder.CalculateTotalOrderPrice()); Console.WriteLine(); var preOrder = new PreOrder(); Console.WriteLine(preOrder.CalculateTotalOrderPrice()); Console.WriteLine(); } }
This works just fine.
But now, we receive an additional request to allow an additional 10 percent discount to our premium users for the preorder. Of course, we could only change the Preorder
class with one if statement to check if our user is a premium user, but that would break the Open Closed Principle. So, in order to implement this additional request, we are going to start with a Decorator class which is going to wrap our OrderBase
object:
public class OrderDecorator : OrderBase { protected OrderBase order; public OrderDecorator(OrderBase order) { this.order = order; } public override double CalculateTotalOrderPrice() { Console.WriteLine($"Calculating the total price in a decorator class"); return order.CalculateTotalOrderPrice(); } }
Now, we can implement the PremiumPreorder
(Concrete Decorator) class:
public class PremiumPreorder : OrderDecorator { public PremiumPreorder(OrderBase order) : base(order) { } public override double CalculateTotalOrderPrice() { Console.WriteLine($"Calculating the total price in the {nameof(PremiumPreorder)} class."); var preOrderPrice = base.CalculateTotalOrderPrice(); Console.WriteLine("Adding additional discount to a preorder price"); return preOrderPrice * 0.9; } }
In this class, we are calculating the total price of the OrderBase
object but also adding the additional discount behavior.
Finally, we can modify the Program.cs
class:
class Program { static void Main(string[] args) { var regularOrder = new RegularOrder(); Console.WriteLine(regularOrder.CalculateTotalOrderPrice()); Console.WriteLine(); var preOrder = new Preorder(); Console.WriteLine(preOrder.CalculateTotalOrderPrice()); Console.WriteLine(); var premiumPreorder = new PremiumPreorder(preOrder); Console.WriteLine(premiumPreorder.CalculateTotalOrderPrice()); } }
As we can see, with the premiumPreorder
object we are wrapping the preOrder
object to which we add an additional behavior:
Now we can clearly see how our Decorator class wraps the preorder object.
Excellent.
Conclusion
In this article, we have learned:
- What is the Decorator design pattern
- When should we use it
- How to implement this pattern in practice
Clear and concise. An example that, in a few lines, contains the full explanation without digression or unnecessary wording.
Thanks for the tutorial! But wouldn’t it make more sense for the PremiumPreorder class to be called just PremiumOrder? Or if the premium is specific to preorders: have the PremiumPreorder constructor take a Preorder object specifically (instead of OrderBase)? Or would this violate the pattern in some way?
Hello Martin. For this example, the Premium users will get discount only if they preorder something, thus the name of the class. That menas if the order is a regular order no discount should be applied even though the user is a Premium one. But again this is just example specific. Regarding your second question, you don’t want to restrict yourself on a specific type, you want to indroduce a higher level of abstraction, thus the usage of the abstract OrderBase class.
This way the premium members only get 9% additional discount in case of preorders, not 10, because 0.9 * 0.9 = 0.81, not 0.8.
With this in mind, what would be the correct solution using the decorator pattern?
No, this situation is different. If a price is 100 than first 10% is 100 * 0.9 which is 90. And that is for preorder. But premium users will get additional 10% on the lower price, so 90 * 0.9 which is 81. 9 is 10% from the 90. But never the less, this is not an issue to talk about, those are just numbers, the main thing is the pattern and how to implement it. And this is a good way to do that.
What is the advantage OrderDecorator when i can directly inheritance from OrderBase in PremiumPreorder
?
As we said in the article, you can extend behavior of your objects with the decorator class dinamically.