Using Decorator pattern in .NET C#

Here is a simple application that demonstrates using of decorator pattern in C#.


Sample solution can be downloaded from here Solution is compatible with Visual Studio 2012, MonoDevelop 5, and Xamarin Studio 5.

Application is based on the answer found at Stackoverflow and this related article “Meanwhile… on the command side of my architecture“. Decorated functionality in this application is logging. This is probably not the best practice to use decorator for cross-cutting functionality such as logging but it is just for example purpose. Actually I prefer to use AOP for cross-cutting functionality such as logging in production and here is my another post about it “Logging in .NET using AOP with PostSharp“.

Here is a trivial method that should be decorated with logging

Including the logging (or any other additional functionality) into Echo method is a violation of the SOLID principles:

  1. The Single Responsibility Principle. If a class does logging together with main functionality then it has multiple responsibilities. We should extract the logging into its own class.
  2. The Open-Closed principle. The behaviour of the system should be able to be altered without changing any existing line of code. For instance when we need logging and also need exception handling (a third responsibility) we must alter the Echo method, which is a violation of the Open-Closed principle.

1. Simple decorator
We extract logging into its own class – the decorator – and allow that class to wrap the original EchoCommand class. Note that method that will be decorated must be virtual public virtual void Echo(string str) because it will be overridden in decorator.

By wrapping the decorator around the real instance, we can now add this logging behaviour to the class, without any other part of the system to change:

When writing the code as shown above, we will have to define decorators for every operation that needs logging in a program. This leads to a lot of duplicate decorator code. This is a violation of the DRY principle. What’s missing here is a common abstraction over all use cases that need to be decorated.

2. General decorator
What’s missing in the previous section is a common interface what every class that implements it executes a command which can be decorated. Let it be called command handler ICommandHandler<TCommand> interface. Let’s define it:

Now we need to pass parameters to command through command handler. Let’s store the method arguments of the Echo(string str) in a separate class. Thus we split data and behaviour.

And let’s put the behaviour of the Echo() method in a class that itself implements ICommandHandler<EchoCommand>:

First of all this gives us possibility to instance EchoCommandHandler once (with a factory or mock it in a unit test) and then feed it with data we need. Then since we have a common interface for all commands we can stack them together and execute in a kind of common manner.

Let’s write now a general abstraction for log decoration that will also be a command handler ICommandHandler:

This LogDecorator class class can be reused for all command handlers in the system. But every method that will use this decoration must be wrapped into a command handler. Use of it:

3. More decorators
If the are more then one decorator in the program then the common part of functionality of them can be extracted into a parent class:

Let’s add a new decorator that will beep a sound after a method call:

Let’s apply both BeepDecorator and LogDecorator to our EchoCommand:

4. Conclusion
Decoration pattern in C# needs quite a lot of code just to wrap the main functionality into a command handler. That can be quite much coding for the whole application to decorate cross-cutting functionality such as logging for every class needs it. So as I mentioned in the beginning I do not like to use decorators for cross-cutting functionality. AOP solves this problem much more elegant. However decoration can be quite useful for additional functionality for some classes, not the whole app.

Sample solution can be downloaded from here Solution is compatible with Visual Studio 2012, MonoDevelop 5, and Xamarin Studio 5.