The Decorator! Detailed Look at Design Pattern

Nermin Kaharovic

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:

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.

Possible combinations

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:

POC project structure

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:

Result

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:

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

After you leave a comment, it will be held for moderation, and published afterwards.


The reCAPTCHA verification period has expired. Please reload the page.