The Decorator Pattern — A simple guide

Isaac Cummings
4 min readAug 10, 2018

--

Photo by Nikunj Gupta on Unsplash

The decorator pattern has a combination of usefulness and low cost that is indispensable when designing software. It allows you to separate cross cutting concerns transparently to the user of an interface. Sometimes its used as a more flexible substitution for inheritance. The decorator pattern tends to compliment other design patterns that assist with construction, for example the Factory Pattern, or the Builder Pattern. For our purposes we will create a simple builder to go with our decorator.

First, some notes about the repository. A full sample of the code used can be found here. Please understand that the sample is intended to demonstrate the decorator pattern, and not be a complete solution for data access. If anyone notices a mistake (or even just a possible improvement) in the sample, please let me know in the comments and i will correct it.

Suppose you have a repository interface that looks like this:

public interface IRepository<TModel> 
{
void Add(TModel model);
void Delete(TModel model);
void Update(TModel model);
TModel GetById(int id);
}

I know that this is pretty anemic for data access but the focus of this post is the design pattern, so we are going to stick with a simple interface. Now, suppose you have two implementors of the interface. One that uses a structured database as a store, and the other uses an in memory store. For any one of these methods you could have quite a bit going on. You want to store the data of course, but then you probably also want to do all kinds of logging, validation, and possibly even authorization. For the SQL implementor, lets use Entity Framework, for simplicity.

public class EFRepository<TModel> : Interfaces.IRepository<TModel>
where TModel : BaseEntity
{
DbContext context;
public EFRepository(DbContext context)
{
this.context = context;
}
public void Add(TModel model)
{
context.Set<TModel>().Add(model);
context.SaveChanges();
}
public void Delete(TModel model)
{
context.Set<TModel>().Remove(model);
context.SaveChanges();
}
public TModel GetById(int id)
{
return context.Set<TModel>()
.FirstOrDefault(m => m.Id == id);
}
public void Update(TModel model)
{
var set = context.Set<TModel>();
set.Attach(model);
context.SaveChanges();
}
}

The in memory implementor could basically just hold a dictionary of keys to models. This implementation assumes the client is responsible for key management, but it wouldn’t be difficult to add an incrementing key.

public class InMemoryRepository<TModel> :
Interfaces.IRepository<TModel>
where TModel : BaseEntity
{
Dictionary<int, TModel> datastore =
new Dictionary<int, TModel>();
public void Add(TModel model)
{
if (datastore.ContainsKey(model.Id))
{
throw new InvalidOperationException("A model with this key already exists in the store");
}
datastore.Add(model.Id, model); } public void Delete(TModel model)
{
if (!datastore.ContainsKey(model.Id))
{
throw new InvalidOperationException("A model with this key doesnt exist in the store");
}
datastore.Remove(model.Id);
}
public TModel GetById(int id)
{
if (!datastore.ContainsKey(id))
{
throw new InvalidOperationException("A model with this key doesnt exist in the store");
}
return datastore[id];
}
public void Update(TModel model)
{
if (!datastore.ContainsKey(model.Id))
{
throw new InvalidOperationException("A model with this key doesnt exist in the store");
}
datastore[model.Id] = model;
}
}

The first thing you might notice is that both implementations are pretty anemic, neither of them implement any features other than actually saving and loading data. This is where a decorator comes in. A decorator implements the interface and requires an implementation of the interface. It then calls back to the base implementation for each method or function while adding its little bit of ‘decoration’. Lets look at logging for example.

First of all the decorator needs something to decorate. So it’s constructor needs to accept an IRepository<TModel> interface. For our purposes our logging decorator will accept an interface for doing logging as well. So the constructor would look like this:

public LoggingDecorator(
IRepository<TModel> repository,
ILogger logger)
{
this.repository = repository;
this.logger = logger;
}

Now, lets implement the ‘Add’ method. Lets say at the beginning of the method we want to call out to the log that something is being added: this.logger.LogMessage(“Adding a model”); after that we can call the add method of our passed in repository this.repository.Add(model) but, lets wrap that in a try catch, and log any exceptions:

try
{
this.repository.Add(model);
}
catch (Exception ex)
{
logger.LogError(ex.ToString());
throw;
}

one of the key features of a decorator is that the interface does not change

Notice that we make sure to re-throw the exception. This is important so that any other decorators have an opportunity to act on exceptions as well. The possibility of an exception is part of the interface, and one of the key features of a decorator is that the interface does not change. If you wanted to get more granular, you could separate out the logging of exceptions into a different decorator.

Do this for each of the methods and you have a decorator! The reason why this so powerful is that now we have logging abstracted away from the implementation of the specific repository. Whether we use the EFRepository, or the InMemoryRepository, we can provide logging by wrapping it in the logging decorator. This also gives us the ability to easily turn logging on or off, simply by using the decorator. Using the Builder Pattern to create and combine the decorators can be a good way to manage the added complexity of combining and ordering decorators. Consider being able to construct your repository like this:

var repository = Builders.RepositoryBuilder<SomeModel>.Create()
.WithDataStore()
.InMemory()
.WithValidator(validator)
.WithAuthorization(authorizer, currentUser)
.WithLogging(logger)
.CreateRepository();

Some final notes on decorators: The order is important! Notice that our logging decorator is listed last. This is because it catches exceptions and logs them. If you want an exception that is thrown by the validator to be logged by the logging decorator then the logging decorator needs to wrap the validator decorator. Also consider that a decorator has to accept anything that it needs to perform its task to be constructor injected.

--

--

Isaac Cummings

I’m a software developer, architect and otherwise average guy. I believe we learn the most through writing, and teaching.