The Decorator! Detailed Look at Design Pattern
Key takeaways
- The decorator is a structural design pattern that uses composition instead of inheritance
- It provides a flexible alternative to sub-classing for extending functionality at runtime
- By enforcing the open-close principle it promotes code extension
Introduction
The decorator design pattern is a structural pattern that enables us to add/change object behavior at runtime. If we refer to SOLID principles, more precisely open-closed responsibility principle, classes should be open for extension but closed for modification. In other words, we should be able to add new behavior to existing classes but we will prevent any modification of existing code. This is exactly what decorator pattern does, so let’s take a look at pattern UML diagram:
If you take a look at the diagram you’ll see that decorator has:
- Component — This is basically an interface that describers behavior of concrete component as well as decorator. Depending on the existing project structure, this could be an interface or abstract class.
- ConcreteComponent — The actual object in which the new functionalities can be added dynamically. We can also wrap up decorators with other decorators.
- Decorator — Defines the interface for all the dynamic functionalities that can be added to the concrete component. The decorator IS a component and also HAS a component. This way components and decorators are interchangeable.
- ConcreteDecorator — Describes all the functionalities that can be added to the concrete components.
Basically, the decorator is a wrapper around concrete components or another decorator. This way we can use composition instead of inheritance to share behavior between different components.
POC
We have already mentioned that decorator is an alternative to sub-classing, so why not simply use sub-classing? Well, imagine we need 5 different functionalities (A; B; C; D; E) for our application. This means we need 5 sub-classes. Now imagine that we also need to support all possible combinations for two functionalities. If we follow the formula for calculating all possible combinations, we’ll get that there are 10 combinations! In other words, we need to create ten more sub-classes for each combination…
List of all possible functionalities (every decorator represents the unique functionality):
- ConcreteDecoratorA ConcreteDecoratorB
- ConcreteDecoratorA ConcreteDecoratorC
- ConcreteDecoratorA ConcreteDecoratorD
- ConcreteDecoratorA ConcreteDecoratorE
- ConcreteDecoratorB ConcreteDecoratorC
- ConcreteDecoratorB ConcreteDecoratorD
- ConcreteDecoratorB ConcreteDecoratorE
- ConcreteDecoratorC ConcreteDecoratorD
- ConcreteDecoratorC ConcreteDecoratorE
- ConcreteDecoratorD ConcreteDecoratorE
You can see that we are going to end up with a lot of classes, so this is where our pattern kicks in! Let’s take a look at code:
Component
public interface IComponent
{
void Operation();
}
Concrete component
public class ConcreteComponent : Interface.IComponent
{
public void Operation()
{
Console.WriteLine("ConcreteComponent.Operation()");
}
}
Decorator
public abstract class Decorator : Interface.IComponent
{
protected Interface.IComponent component;public void SetComponent(Interface.IComponent component)
{
this.component = component;
}public virtual void Operation()
{
if (component != null)
{
component.Operation();
}
}
}
Concrete decorator
Example of a concrete decorator (A):
public class ConcreteDecoratorA : Decorator.Decorator
{
public override void Operation()
{
base.Operation();
Console.WriteLine("ConcreteDecoratorA.Operation()");
}
}
Main method
static void Main(string[] args)
{
Console.WriteLine("<<< Concreate component >>>");// Concreate component
var concreateComponent = new
ConcreteComponent.ConcreteComponent();concreateComponent.Operation();Console.WriteLine("\n<<< Concrete decorator A >>>");// Concrete decorator A
var concreteDecoratorA = new
ConcreteDecorators.ConcreteDecoratorA();concreteDecoratorA.Operation();
Console.WriteLine("\n<<< Concrete decorator B >>>");
// Concrete decorator B
var concreteDecoratorB = new
ConcreteDecorators.ConcreteDecoratorB();
concreteDecoratorB.Operation();
Console.WriteLine("\n<<< Concrete decorator C >>>");
// Concrete decorator C
var concreteDecoratorC = new
ConcreteDecorators.ConcreteDecoratorC();concreteDecoratorC.Operation();
Console.WriteLine("\n<<< Concrete decorator D >>>");
// Concrete decorator D
var concreteDecoratorD = new
ConcreteDecorators.ConcreteDecoratorD();concreteDecoratorD.Operation();Console.WriteLine("\n<<< Concrete decorator E >>>");
// Concrete decorator E
var concreteDecoratorE = new
ConcreteDecorators.ConcreteDecoratorE();concreteDecoratorE.Operation();
Console.WriteLine("\n<<< Decorated Component with decorators:
A, B >>>");
// Decorated Component
concreteDecoratorA.SetComponent(concreateComponent);
concreteDecoratorB.SetComponent(concreteDecoratorA);
concreteDecoratorB.Operation();Console.WriteLine("\n<<< Decorated Component with decorators:
A, C >>>");
// Concreate component
concreateComponent = new ConcreteComponent.ConcreteComponent();// Decorated Component
concreteDecoratorA.SetComponent(concreateComponent);
concreteDecoratorC.SetComponent(concreteDecoratorA);
concreteDecoratorC.Operation();Console.WriteLine("\n<<< Decorated Component with decorators:
C, D, E >>>");
// Concreate component
concreateComponent = new ConcreteComponent.ConcreteComponent();
// Decorated Component
concreteDecoratorC.SetComponent(concreateComponent);
concreteDecoratorD.SetComponent(concreteDecoratorC);
concreteDecoratorE.SetComponent(concreteDecoratorD);
concreteDecoratorE.Operation();Console.ReadKey();
}
Result
Here is what the result looks like:
In the given example we can see the main advantage of the decorator pattern is the ability to attach additional functionality at run-time and also it enables us to decorate our component with different functionalities in a clean and reusable way.
Real-world example
So far we have seen how decorator works and what are the main advantages of using. Now let’s apply the acquired information to a real-world scenario. To gain a broader overview of the topic, check out the blogs I wrote earlier:
- Designing Flexible And Cross-Platform API Using Asp.Net Core — Part 1
- Designing Flexible And Cross-Platform API Using Asp.Net Core — Part 2
because we’ll be using the existing code base to demonstrate usage of design pattern. We are going to decorate existing repository in .Net Core API and add caching and logging functionality. Let’s start!
Component
In our example, we’ll use IBookRepository as a starting point. This is our Component.
public interface IBookRepository
{
Task<IEnumerable<Book>> GetAllAsync();
Task<Book> GetByIdAsync(int id);
Task CreateAsync(Book value);
Task UpdateAsync(Book value);
Task DeleteAsync(int value);
}
Concrete component
BooksRepository represents the concrete component.
public class BooksRepository : GenericRepository<Book>, IBookRepository
{
public BooksRepository(DataContext context) : base(context) { }
public async Task<IEnumerable<Book>> GetAllAsync()
{
return await FindAllAsync();
}public async Task<Book> GetByIdAsync(int id)
{
var books = await FindByConditionAync(o => o.Id == id);
if (books == null)
return null;// Search by Id will always return one record
return books.SingleOrDefault();
}public async Task UpdateAsync(Book book)
{
Update(book);
await SaveAsync();
}public async Task DeleteAsync(int id)
{
var book = await this.GetByIdAsync(id);
Delete(book);
await SaveAsync();
}public async Task CreateAsync(Book book)
{
Create(book);
await SaveAsync();
}
}
The decorator
Now, let’s create the decorator class:
public abstract class BaseDecorator : IBookRepository
{
protected IBookRepository libraryItem;public BaseDecorator(IBookRepository libraryItem)
{
this.libraryItem = libraryItem;
}
public async Task CreateAsync(Book value)
{
await libraryItem.CreateAsync(value);
}public async Task DeleteAsync(int value)
{
await libraryItem.DeleteAsync(value);
}public virtual async Task<IEnumerable<Book>> GetAllAsync()
{
return await libraryItem.GetAllAsync();
}public async Task<Book> GetByIdAsync(int id)
{
return await libraryItem.GetByIdAsync(id);
}public async Task UpdateAsync(Book value)
{
await libraryItem.UpdateAsync(value);
}
}
Concrete decorators
We have created the component, concrete component and decorator, so now let’s create concrete decorators. As I already mentioned, we are going to add two new functionalities to our repository (caching — caching decorator and logging — logging decorator).
Caching
Here is an example of caching decorator for GET method:
public class CachingDecorator : BaseDecorator
{
IMemoryCache _cache;
public CachingDecorator(IBookRepository libraryItem,
IMemoryCache cache) :
base(libraryItem)
{
_cache = cache;
}public override async Task<IEnumerable<Book>> GetAllAsync()
{
IEnumerable<Book> cacheEntry;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
{
// Key not in cache, so get data.
cacheEntry = await libraryItem.GetAllAsync();// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(10));// Save data in cache.
_cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
return cacheEntry;
}
return (IEnumerable<Book>)_cache.Get(CacheKeys.Entry);
}
}
Logging
Here is an example of logging decorator for GET method:
public class LogingDecorator : BaseDecorator
{
public LogingDecorator(IBookRepository libraryItem) :
base(libraryItem) { }public override async Task<IEnumerable<Book>> GetAllAsync()
{
// Log data ...
return await libraryItem.GetAllAsync();
}
}
Register decorator
The dependency injection framework in .NET Core is pretty good but out-of-the-box it doesn’t support this pattern very well. We have to write extra some code in order to make everything working. Here is an article which explains different approaches as well as their pros and cons: Decorators in .NET Core with Dependency Injection.
Let’s create a helper class:
public static class ServiceCollectionExtensions
{
public static void Decorate<TInterface, TDecorator>(this ˛
IServiceCollection services)
where TInterface : class
where TDecorator : class, TInterface
{
// grab the existing registration
var wrappedDescriptor = services.FirstOrDefault(
s => s.ServiceType == typeof(TInterface));
// check it's valid
if (wrappedDescriptor == null)
throw new InvalidOperationException($"
{typeof(TInterface).Name} is not registered");
// create the object factory for our decorator type,
// specifying that we will supply TInterface explicitly
var objectFactory = ActivatorUtilities.CreateFactory(
typeof(TDecorator),
new[] { typeof(TInterface) });
// replace the existing registration with one
// that passes an instance of the existing registration
// to the object factory for the decorator
services.Replace(ServiceDescriptor.Describe(
typeof(TInterface),
s => (TInterface)objectFactory(s, new[] {
s.CreateInstance(wrappedDescriptor) }),
wrappedDescriptor.Lifetime)
);
} private static object CreateInstance(this IServiceProvider
services, ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance != null)
return descriptor.ImplementationInstance;
if (descriptor.ImplementationFactory != null)
return descriptor.ImplementationFactory(services);
return
ActivatorUtilities.GetServiceOrCreateInstance(services,
descriptor.ImplementationType);
}
}
Inject decorators
Now we should be able to register our decorators with a few lines of code:
services.Decorate<IBookRepository, CachingDecorator>();
services.Decorate<IBookRepository, LogingDecorator>();
That’s it!
Hopefully, this article helped you grasp the basic concepts behind the decorator pattern and how to apply it to the real-world scenarios. We have decorated repository with caching and logging features in a clean and reusable way.
Feel free to share any thoughts or questions you may have.