Introduction
This tip will talk about the decorator pattern and its use in real world applications.
What is Decorator Design Pattern
Decorator pattern attaches additional responsibilities to the object at runtime. Decorator provides flexible alternative to subclassing for extending functionality. Basically, we use this pattern when the basic functionality for the class is already implemented/tested, but we want to provide some additional functionality (depends on requirement change) along with the basic one. While adding new functionality, we don’t want to make changes in the existing classes as it is well tested and we don’t want to violate Open-Closed principle.
Using the Code
Now, let’s take a look at the example. Here, we are working in Data Access Layer (DAL). We are basically performing CRUD (Create, read, update and delete) operation on Person
Entity. We can basically fetch/update Person
entity from two repositories, either SQL repository or WebAPI repository. To implement this change, we will create IDataAccess
interface as below:
public interface IDataAccess
{
bool CreatePerson(Person person);
bool UpdatePerson(int id, Person person);
bool DeletePerson(int id);
Person SearchPerson(int id);
}
Now, we will implement our two repositories, SQLRepository
and WebAPIRepository
as below.
public class SQLDataAccess : IDataAccess
{
public bool CreatePerson(Person person)
{
}
public bool UpdatePerson(int id, Person person)
{
}
public bool DeletePerson(int id)
{
}
public Person SearchPerson(int id)
{
}
}
public class WebAPIDataAccess : IDataAccess
{
public bool CreatePerson(Person person)
{
}
public bool UpdatePerson(int id, Person person)
{
}
public bool DeletePerson(int id)
{
}
public Person SearchPerson(int id)
{
}
}
I haven't provided any implementation for both of the classes as it is not important here. Now, let's imagine our above SQL and WebAPI implementations are working fine and well tested. Now, we have requirement change request for our existing implementation. We want to support caching as SearchPerson
method is taking time for returning the result.
Now, how we will support caching in our existing implementation. Possible solution (without implementing decorator pattern) is to directly support caching in both repositories, but the disadvantages with this approach are as follows:
- It will violate DRY (Do not Repeat Yourself) principle since we will be implementing the same code at two different locations. Also, if we want to change our caching logic, we need to make changes in both the classes.
- It will violate single responsibility group. Since
SQLDataAccess
and WebAPIDataAccess
both should only support fetching data from respective repository. Not any other thing. - It may break our existing functionality, so we might need to test everything from scratch.
Now, here the decorator pattern will come to our rescue. :):)
How we will create new class MemoryCacheDataAccess
derived from IDataAccess
is shown below:
public class MemoryCacheDataAccess : IDataAccess
{
private readonly IDataAccess _dataAccess;
private Dictionary<int, Person> _memoryCache = new Dictionary<int, Person>();
public MemoryCacheDataAccess(IDataAccess dataAccess)
{
_dataAccess = dataAccess;
}
public bool CreatePerson(Person person)
{
var isCreated = _dataAccess.CreatePerson(person);
if (isCreated)
_memoryCache.Add(person.Id, person);
return isCreated;
}
public bool UpdatePerson(int id, Person person)
{
var isUpdated = _dataAccess.UpdatePerson(id, person);
if (isUpdated)
{
if(_memoryCache.ContainsKey(person.Id))
{
_memoryCache[person.Id] = person;
}
else
{
_memoryCache.Add(person.Id, person);
}
}
return isUpdated;
}
public bool DeletePerson(int id)
{
var isDeleted = _dataAccess.DeletePerson(id);
if(isDeleted)
{
if (_memoryCache.ContainsKey(person.Id))
{
_memoryCache.Remove(id);
}
}
return isDeleted;
}
public List<Person> SearchPerson(int id)
{
if (_memoryCache.ContainsKey(id))
return new List<Person> { _memoryCache[id] };
return _dataAccess.SearchPerson(id);
}
}
Now, the interesting thing about this class is that we are passing IDataAccess
interface reference to the constructor. So we can easily create wrapper around SQLDataAccess
or WebAPIDataAccess
. For all the operations, we will be calling actual dataaccess
method, once result is back from the actual repository, we are storing this into our cache. The actual code invocation is as below:
static void Main(string[] args)
{
IDataAccess sqlDataAccess = new SQLDataAccess();
IDataAccess cacheDataAccess = new MemoryCacheDataAccess(sqlDataAccess);
}
So in this example, we have attached additional responsibility (i.e., caching support) to our dataaccess
without making changes in the actual classes. In the similar fashion, we can add more wrappers around our existing dataaccess
classes like ExceptionHandlerDataAccess
, PerformanceMonitorDataAccess
, etc. Invocation for this wrapper will be as below:
static void Main(string[] args)
{
IDataAccess sqlDataAccess = new SQLDataAccess();
IDataAccess cacheDataAccess = new MemoryCacheDataAccess(sqlDataAccess);
IDataAccess exceptionHandlerDataAccess = new ExceptionHandlerDataAccess(cacheDataAccess);
}
I hope you have understood the decorator design pattern. Please share your feedback in the comments below.
History
- 17th January, 2016: Initial version