Design Patterns: Decorator
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.
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.
For our abstraction, we’re going to create a service that retrieves the weather for us:
In the above, we have an interface
IWeatherGettinator that has a single method, which takes no arguments, and returns the weather; simple enough.
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
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
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
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:
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
IWeatherGettinator while making use of the
StopWatchDecoratorWeather would look like this:
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
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
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
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!
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
We can now try out our new decorator like this:
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?
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!
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.
Design Patterns: Decorator