Introduction
The decorator pattern is a structural design pattern which allows you to dynamically add functionality to classes without modifying the target class.
It does this by creating a wrapper around the original class, which gives you an extension point to inject in new behavior.
It allows you to maintain a strong separation of concerns (SoC) in your code-base but also gives you the flexibility to add, remove or resequencing behaviors as needed.
We can use multiple decorators and nest them to build powerful processing pipelines.
General Use Cases
- When applying Cross-Cutting Concerns (such as logging, performance tracking, caching, authorization etc.)
- When modifying data send to and from a component.
UML
Here is general structure of this pattern:
As you can see that a decorator wraps a component.
Decorator Example # 1
Lets see a very basic example.
I have defined the component and concrete component classes as shown below:
Component
Here is the code which uses this component:
So far this is typical C# code and a very simple component.
Lets say, we have a requirement to provide AddMeat functionality to SimpleSandwich. We can use decorator pattern for this new functionality:
Decorator
Here is the code for abstract and concrete decorators
As you can see that we’ve added new functionality without changing the base object.
Here is the usage and the output:
Similar to MeatDecorator, I’ve added another Decorator (DressingDecorator) and here is the calling code and output:
This start to feel like a creational pattern, but notice, what we are doing is adding functionality. We are just doing that through constructors and using composition in these decorators.
So although it feels here like a creational pattern, its actually a structural pattern because we are modifying the structure of simple sandwich by utilizing a decorator and we got a lot of power into that hierarchy of that decorator.
As you can see that we are using inheritance in decorator but we don’t have to change our base object.
Framework Usages
Decorator pattern is very common in framework components as well, e.g. .NET Stream class also implements decorator pattern:
Lets see few more example of decorator pattern.
Decorator Example # 2
Here we will see how to implement cross-cutting concerns. For the demo purposes, I’ve created following interface and commands:
So we have various commands and which perform some operations.
Here is the command usage and output in the main method:
So these command objects are performing some operations. Lets see how decorator pattern can help with various cross-cutting concerns such as logging, caching, database retries etc.
Our focus here is not the actual commands implementation, but how the decorator pattern is helping us extending functionality, same time resulting in more manageable code which adheres to separation of concerns (SoC) principle.
Logging Decorator
Now, lets say we have a requirement that when executing these commands, we also want to log some information such as how often the command is called, how long it took, parameters and/or responses etc. about this command (or other commands).
One way to do it to change the command code to add this functionality, but this will result in a lot of duplicated code, separation of concerns is not managed, future requirements will result even in more issues.
Let’s apply decorator pattern and see how it will result in better structure and more maintainable code:
Similar to Example 1. Here LoggingDecorator is wrapping the actual object. Then dealing with logging concerns here. Also notice, that we can change actual component’s output if needed.
Here is how the main method and the output looks like:
For other commands, we just need to wrap those in LoggingDecorator and they will also have this logging functionality added to them.
So, this is a typical structure. We introduced new code to do logging but we can do other things as well based on our requirements e.g. modify values that are going to the inner-calls, could also modify response or take some other actions etc.
Caching Decorator
Lets see another example i.e. a caching decorator. For example, you can use caching to reduce number of API or database calls etc.
For demo, we’ll use .NET Core’s InMemoryCache. I’ve added the following nu-get package to the project. You can find more details on the official website about this extension.
Here is the Caching Decorator class:
You can check the source code to see how execute method is dealing with caching requirement. Here for us important thing is that, we put this caching concern in a separate class and now this concern can be managed separately from the actual command.
Here is the calling code and the output:
As you can see that first time data was retrieved from actual execution of command (it can be database read, REST call to external API etc.). But for later calls, data was retrieved from cache.
Now, lets combine this with logging decorator to build a simple pipe-line of these operations:
You may have some other cross-cutting requirement e.g. database retries or the likes, decorator pattern can help in managing those concerns. You can also sequence the operations easily and have a lot of flexibility in managing future requirements.
FAQs
Question: What if your component doesn’t have an interface or extend some base class, what you can do then?
Answer: You can extract an interface from the class and have your class and your decorator implement that interface.
Question: What if you don’t own the class?
Answer: Then you can use an adapter pattern to put a class in front of your component and then have the adapter pattern implement the interface you have extracted.
Pitfalls
- New class for every feature added.
- Multiple smaller objects (may be not a bad thing).
- Often consumed with simple inheritance.
Summary
Being able to layer objects together in this onion-type structure and then intercept and modify method class is a very powerful idea because it allows us to separate concerns and dynamically add new functionality when we new needs come up.
Following are the key characteristics of decorator pattern:
- Original object can stay the same.
- Unique way to add functionality.
- Sometimes confused with inheritance.
- Can be a little bit complex for clients.
You can download the source-code for the demos from this git repository.
Let me know if you have some comments or questions. Till next time, Happy Coding.
My Recent Books
Discover more from Hex Quote
Subscribe to get the latest posts sent to your email.
1 thought on “Extending functionality using Decorator Pattern (C#)”
Comments are closed.