Decorator Pattern
A structural design pattern used for dynamically adding behavior to a class without making changes to that class.
The Decorator Class will take an object implementing the same interface. This allows us to pass the object being decorated into the decorator object and allows the decorator object to act as a wrapper
around this original object.
The Decorator object will keep a reference of the object being decorated(the component object). Because the decorator object implement the same interface as the original component object, it now has a chance to intercept any method calls on the interface and inject some additional behavior into those calls.
Decorator Class and be nested.
This is the Example we are going to use.
Using Decorator Objects
1 | // Standard component instantiation |
To achieve this, we need to make sure the original component class and all the decorator classes need to implement from the same interface. And all decortor classes need to take the object of type IWeatherService in their constructors.
Logging Decorator
Log how often a method was called, how long it took, parameters and responses.
1 | public interface IWeatherService |
This is the interface for our original WeatherService Class and our new Decorator Class
1 | public class WeatherServiceLoggingDecorator : IWeatherService |
This is our new Decorator Class, we implement from the IWeatherService Interface, it is taking the interface as a parameter in the constructor, and implemented two methods. In the GetCurrentWeather() method, it logs the time it takes to run the method, then calling the original _weatherService.GetCurrentWeather() method.
Caching Decorator
Cache weather conditions, forecasts for a city to reduce the number of external API calls.
1 | public class WeatherServiceCachingDecorator : IWeatherService |
And meanwhile in the HomeController, we need to build this onion like structure from inside to outside.
1 | IWeatherService weatherService = new WeatherService(apiKey); |
The call stack will be: CachingDecorator => LoggingDecorator => WeatherService
Decorator Summary
- Multiple decorators can be used in conjunction with one another
- Each decorator can focus on a single task, promoting separation of concerns
- Decorator classes allow functionality to be added dynamically
Decorator Pattern Characteristics
- Implement the same base interface as the original object
- Take a instance of the original object as part of their constructor
- Add new behaviors to the original object they are wrapping
Using Decorators with Dependency Injection Container
.NET Core has built in IoC container which will help us to create WeatherService object when we need it and manage the lifetime of object.
We could simplify the HomeController constructor to this
1 | private readonly IWeatherService _weatherService; |
And in the startUp.cs, we configure the IoC container to this
1 | public void ConfigureServices(IServiceCollection services) |
Now whenever we need a IWeatherService object, it will be created and provided to us with this structure. (CachingDecorator => LoggingDecorator => WeatherService)
When to use Decorator Pattern
- Cross cutting concerns
- Logging, Performance Tracking(Timer, StopWatch…), Caching, Authorization
- Manipulate data going to/from component
- object we need to encrypt and decrypt before being passed to a component
Question: What if your component does not have an interface/extend from a base class?
- Extract an interface from the class
What if you can’t modify the class?
- Adapter Pattern
To put a class in front of your component and extract an interface from the Adapter Class
Summary
- Design Patterns are about ideas
- Interfaces allow us to create loosely coupled designs
- the decorator pattern adds the ability to dynamically add behavior
- This is accomplished by wrapping around the original object and intercepting methods