The decorator pattern is a structural design pattern that can be used to add functionality to classes without modifying the original class or its interface.
The Decorator Pattern
From Wikipedia:
In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern. The decorator pattern is structurally nearly identical to the chain of responsibility pattern, the difference being that in a chain of responsibility, exactly one of the classes handles the request, while for the decorator, all classes handle the request.
“Prefer composition over inheritance” might be something you’ve heard of before, and I feel like the decorator pattern is basically the epitome of this saying. Decorators are themselves implementations of an abstraction, that also depend on the abstraction itself. This allows for “composable” pieces of behavior in that you would likely have a single “main” implementation of an abstraction, then separate “decorator” implementations that build on top of it.
I’m not sure if that made sense, so hopefully an example will help clear things up.
Implementing the decorator pattern
The abstraction
For our abstraction, we’re going to create a service that retrieves the weather for us:
public interface IWeatherGettinator
{
Task<Weather> GetWeather();
}
public class Weather
{
public Guid Id { get; }
public int FreedomUnits { get; set; }
public bool ItGonRain { get; set; }
public Weather()
{
Id = Guid.NewGuid();
}
public override string ToString()
{
var sb = new StringBuilder()
.AppendLine("The current weather:")
.AppendLine($"\tId: {Id}")
.AppendLine($"\tTemperature (in freedom units): {FreedomUnits}")
.AppendLine($"\tIt gon rain? {ItGonRain}");
return sb.ToString();
}
}
In the above, we have an interface IWeatherGettinator
that has a single method, which takes no arguments, and returns the weather; simple enough.
The base implementation
For this example, we’re going to pretend that to get the weather is an expensive operation, just because it makes one of the decorators of IWeatherGettinator
more interesting and easier to see the point of it all (IMO).
So for the implementation of the IWeatherGettinator
:
public class WeatherGettinator : IWeatherGettinator
{
private readonly Random _random;
public WeatherGettinator(Random random)
{
_random = random;
}
/// <summary>
/// Gets the weather, mimics a long running task.
/// </summary>
/// <returns><see cref="Task"/> of <see cref="Weather"/></returns>
public async Task<Weather> GetWeather()
{
await Task.Delay(5000);
return new Weather()
{
FreedomUnits = _random.Next(0, 100),
ItGonRain = _random.Next(0, 1) == 1
};
}
}
On getting the weather (which is a ~5 second operation), we get a random temperature and a value that indicates if it’s going to rain or not. Each time GetWeather
is invoked, it takes another 5 seconds to retrieve a random “weather” instance.
The above implementation should look something like this when you ToString
the retrieved Weather
via a GetWeather
invoke:
Our first decorator
Getting the weather seems to take a pretty long time! Perhaps we can introduce our first decorator to get some timing information on the implementation of IWeatherGettinator
:
public class StopWatchDecoratorWeather : IWeatherGettinator
{
private readonly IWeatherGettinator _weatherGettinator;
public StopWatchDecoratorWeather(IWeatherGettinator weatherGettinator)
{
_weatherGettinator = weatherGettinator;
}
public async Task<Weather> GetWeather()
{
Stopwatch sw = Stopwatch.StartNew();
var weather = await _weatherGettinator.GetWeather();
sw.Stop();
Console.WriteLine($"Decorated IWeatherGettinator ran for {sw.ElapsedMilliseconds}ms");
return weather;
}
}
This might look a little strange, so let’s go over it a bit. The StopWatchDecoratorWeather
is an implementation of IWeatherGettinator
that also depends on an implementation of IWeatherGettinator
. You can see that the method implementation GetWeather
starts a stop watch, calls the injected implementation of IWeatherGettinator
, stops the stopwatch, and writes to the console the length of time the operation took.
Now you probably wouldn’t actually write a decorator exactly like the above, but you could do something like it, especially if you were to make use of a logging framework. Logging the above could make more sense, especially if the invoke took more than a certain amount of time.
How does the above get used? Well, you could “compose” your object like so:
IWeatherGettinator weatherGettinator = new StopWatchDecoratorWeather(new WeatherGettinator(new Random()));
You would actually want to be constructing these objects via an IOC container or something similar, this was to just get the point across… also there is some complexity to registering decorated objects with at least the .net core built in IOC container; though you could also use a factory to accomplish it more easily.
What does the above construction mean? Well, we’re instantiating a new instance of a StopWatchDecoratorWeather
which itself depends on a IWeatherGettinator
instance, in this case we’re passing in a concrete implementation of WeatherGettinator
.
Running the IWeatherGettinator
while making use of the StopWatchDecoratorWeather
would look like this:
The issue with inheritance
Inheritance has an issue; an issue that can be solved through composition. I’ve mentioned this several times now, but it’s kind of hard (at least for me) to convey what I mean.
Say we wanted to introduce another piece of functionality; in this post we’re going to be doing it through the use of decorators, but just imagine for a moment that we weren’t.
In c# a class can only ever extend a single other class. If we ever wanted to “mix and match” behaviors of an abstraction that relied on inheritance, that can be extremely difficult to do. If we have an interface IFoo
, with an implementation Foo
, then had separate implementations A
and B
, both of which extended Foo
, how would we go about introducing yet another implementation C
, that needed some of its own unique characteristics, as well as the additional functionality provided by both A
and B
? This would be challenging with an inheritance scenario, but is really trivial when making use of decorators.
To demonstrate that, let’s introduce another decorator to our IWeatherGettinator
.
The second decorator
We’ve done some testing with our StopWatchDecoratorWeather
and determined that we could save a lot of time getting the weather if we introduced some caching. Thankfully, introducing caching via a decorator is very simple, and should look pretty similar to our first decorator!
public class CachedWeatherGettinator : IWeatherGettinator
{
private readonly IWeatherGettinator _weatherGettinator;
private Weather _cachedWeather;
private DateTime _cachedOn;
public CachedWeatherGettinator(IWeatherGettinator weatherGettinator)
{
_weatherGettinator = weatherGettinator;
}
public async Task<Weather> GetWeather()
{
var now = DateTime.Now; // this should be injected for testability, but we're demonstrating a decorator so...
var timeDiffSecondsSinceCachedValue = now.Subtract(_cachedOn).TotalSeconds;
if (_cachedWeather == null || timeDiffSecondsSinceCachedValue > 30)
{
Console.WriteLine("Cached value does not exist or is expired, get a new one.");
_cachedOn = now;
_cachedWeather = await _weatherGettinator.GetWeather();
}
else
{
Console.WriteLine("Using cached value...");
}
return _cachedWeather;
}
}
In our new CachedWeatherGettinator
, we’re making use of some instance state to return the previously retrieved weather if it’s less than 30 seconds since it was retrieved. You’ll notice this implementation, like our first decorator, both implements and depends on IWeatherGettinator
.
We can now try out our new decorator like this:
IWeatherGettinator weatherGettinator = new CachedWeatherGettinator(new WeatherGettinator(new Random()));
And you’ll see that the “first” call to the IWeatherGettinator
will take the 5 seconds, but another call made immediately after will return much faster.
But even more interesting that that, is we can make use of multiple decorators for the same abstraction, the construction of which is considered composition.
What could that look like?
IWeatherGettinator weatherGettinator = new StopWatchDecoratorWeather(new CachedWeatherGettinator(new WeatherGettinator(random)));
We’re “composing” our object by decorating our base WeatherGettinator
with multiple decorators! We have decorated the base object with both a StopWatch
as well as Caching
layer, mostly to demonstrate that the caching layer is in fact working. Let’s take a look!
Reasons to use this pattern
Hopefully it should be pretty clear after going through the post why this pattern can be quite powerful, but just to reiterate:
- Adding additional functionality to an abstraction without changing the abstraction or its concretions.
- Keeping concerns of caching or logging separate from the base implementation. These are taken care of via decorators, allowing your core “business logic” to stay pure, and uncaring about the functionality provided by the decorators.
Top comments (0)