The decorator pattern is a structural design pattern used for dynamically adding behavior to a class without changing the class. You can use multiple decorators to extend the functionality of a class whereas each decorator focuses on a single-tasking, promoting separations of concerns. Decorator classes allow functionality to be added dynamically without changing the class thus respecting the open-closed principle.
You can see this behavior on the UML diagram where the decorator implements the interface to extend the functionality.
When to use the Decorator Pattern
The decorator pattern should be used to extend classes without changing them. Mostly this pattern is used for cross-cutting concerns like logging or caching. Another use case is to modify data that is sent to or from a component.
Decorator Pattern Implementation in ASP .NET Core 3.1
You can find the code of the demo on Github.
I created a IDataTypeService class with a GetData method which returns a list of ints. Inside the loop, I added a Thread.Sleep to slow down the data collection a bit to make it more real-world like.
public class DataService : IDataTypeService
{
public DateTime GetCreateData()
{
throw new NotImplementedException();
}
public List<int> GetData()
{
var data = new List<int>();
for (var i = 0; i < 10; i++)
{
data.Add(i);
Thread.Sleep(350);
}
return data;
}
}
This method is called in the GetData action and then printed to the website. The first feature I want to add with a decorator is logging. To achieve that, I created the DataServiceLoggingDecorator class and implement the IDataTypeService interface. In the GetData method, I add a stopwatch to measure how long collecting data takes and then log the time it took.
public class DataServiceLoggingDecorator : IDataTypeService
{
private readonly IDataTypeService _dataService;
private readonly ILogger<DataServiceLoggingDecorator> _logger;
public DataServiceLoggingDecorator(IDataTypeService dataService, ILogger<DataServiceLoggingDecorator> logger)
{
_dataService = dataService;
_logger = logger;
}
public DateTime GetCreateData()
{
throw new NotImplementedException();
}
public List<int> GetData()
{
_logger.LogInformation("Starting to get data");
var stopwatch = Stopwatch.StartNew();
var data = _dataService.GetData();
stopwatch.Stop();
var elapsedTime = stopwatch.ElapsedMilliseconds;
_logger.LogInformation($"Finished getting data in {elapsedTime} milliseconds");
return data;
}
}
Additionally, I want to add caching also using a decorator. To do that, I created the DataServiceCachingDecorator class and also implemented the IDataTypeService interface. To cache the data, I use IMemoryCache and check the cache if it contains my data. If not, I load it and then add it to the cache. If the cache already has the data, I simply return it. The cache item is valid for 2 hours.
public class DataServiceCachingDecorator : IDataTypeService
{
private readonly IDataTypeService _dataService;
private readonly IMemoryCache _memoryCache;
public DataServiceCachingDecorator(IDataTypeService dataService, IMemoryCache memoryCache)
{
_dataService = dataService;
_memoryCache = memoryCache;
}
public List<int> GetData()
{
List<int> cacheEntry;
const string cacheKey = "data-key";
// Look for cache key.
if (!_memoryCache.TryGetValue(cacheKey, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = _dataService.GetData();
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(30));
// Save data in cache.
_memoryCache.Set(cacheKey, cacheEntry, cacheEntryOptions);
}
//if (_memoryCache.TryGetValue<List<int>>(cacheKey, out var data))
//{
// return data;
//}
//data = _dataService.GetData();
//_memoryCache.Set(cacheKey, data, TimeSpan.FromMinutes(120));
return cacheEntry;
}
}
All that is left to do is to register the service and decorator in the ConfigureServices method of the startup class with the following code:
services.AddScoped(serviceProvider =>
{
var logger = serviceProvider.GetService<ILogger<DataServiceLoggingDecorator>>();
var memoryCache = serviceProvider.GetService<IMemoryCache>();
IDataTypeService concreteService = new DataService();
IDataTypeService loggingDecorator = new DataServiceLoggingDecorator(concreteService, logger);
IDataTypeService cacheingDecorator = new DataServiceCachingDecorator(loggingDecorator, memoryCache);
return cacheingDecorator;
});
With everything in place, I can call the GetData method from the service which gets logged and the data placed in the cache. When I call the method again, the data will be loaded from the cache.
public class ValuesController : ControllerBase
{
private readonly IDataTypeService _dataService;
public ValuesController(IDataTypeService dataService)
{
_dataService = dataService;
}
// GET: api/<ValuesController>
[HttpGet]
public IEnumerable<string> Get()
{
var data = _dataService.GetData();
string[] the_array = data.Select(i => i.ToString()).ToArray();
return the_array;
}
// GET api/<ValuesController>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<ValuesController>
[HttpPost]
public void Post([FromBody] string value)
{
}
// PUT api/<ValuesController>/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] string value)
{
}
// DELETE api/<ValuesController>/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
Be First to Comment