The Decorator Pattern provides a mechanism to embellish the behaviour of an object without changing its essential interface. A decorated object should be able to be substituted wherever the original (non-decorated) object was expected. Decoration typically does not involve modifying the source code of the original object and decorators should be able to be combined in flexible ways to produce objects with several embellishments.
Suppose we have the following
There might be times when it is useful to timestamp a log message, or times when we might want to change the case of the message. We could try to build all of this functionality into our
Logger class. If we did that, the
Logger class would start to be very complex. Also, everyone would obtain all of features even when they might not want a small subset of the features. Finally, feature interaction would become quite difficult to control.
To overcome these drawbacks, we instead define two decorator classes. Uses of the
Logger class are free to embellish their base logger with zero or more decorator classes in whatever order they desire. The classes look like this:
We can use the decorators like so:
You can see that we embellish the logger behaviour with both decorators. Because of the order we chose to apply the decorators, our log message comes out capitalised and the timestamp is in normal case. If we swap the order around, let's see what happens:
Now the timestamp itself has also been changed to be uppercase.
A touch of dynamic behaviour
Our previous decorators were specific to
Logger objects. We can use Groovy's Meta-Object Programming capabilities to create a decorator which is far more general purpose in nature. Consider this class:
It takes any class and decorates it so that any
String method parameter will automatically be changed to lower case.
Just be careful with ordering here. The original decorators were restricted to decorating
Logger objects. This decorator work with any object type, so we can't swap the ordering around, i.e. this won't work:
We could overcome this limitation be generating an appropriate Proxy type at runtime but we won't complicate the example here.
Runtime behaviour embellishment
More dynamic decorating
Suppose we have a calculator class. (Actually any class would do.)
We might be interested in observing usage of the class over time. If it is buried deep within our codebase, it might be hard to determine when it is being called and with what parameters. Also, it might be hard to know if it is performing well. We can easily make a generic tracing decorator that prints out tracing information whenever any method on the
Calc class is called and also provide timing information about how long it took to execute. Here is the code for the tracing decorator:
Here is how to use the class in a script:
And here is what you would see after running this script: