blob: d676d0f4257c378bee73ec25fb6e5e8e3f5cfe81 [file] [log] [blame]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
= Decorator Pattern
The http://en.wikipedia.org/wiki/Decorator_pattern[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.
== Traditional Example
Suppose we have the following `Logger` class.
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_logger_class,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_traditional_classes,indent=0]
----
We can use the decorators like so:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_traditional_usage,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_traditional_usage2,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_dynamic_behaviour_class,indent=0]
----
It takes any class and decorates it so that any `String` method parameter will automatically be changed to lower case.
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_dynamic_behaviour_usage,indent=0]
----
Just be careful with ordering here. The original decorators were restricted to decorating `Logger` objects. This decorator works with any object type, so we can't swap the ordering around, i.e. this won't work:
----
// Can't mix and match Interface-Oriented and Generic decorators
// logger = new TimeStampingLogger(new GenericLowerDecorator(new Logger()))
----
We could overcome this limitation be generating an appropriate Proxy type at runtime but we won't complicate the example here.
== Runtime behaviour embellishment
You can also consider using the `ExpandoMetaClass` from Groovy 1.1 to dynamically embellish a class with behaviour. This isn't the normal style of usage of the decorator pattern (it certainly isn't nearly as flexible) but may help you to achieve similar results in some cases without creating a new class.
Here's what the code looks like:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_runtime_behaviour,indent=0]
----
This achieves a similar result to applying a single decorator but we have no way to easily apply and remove embellishments on the fly.
== More dynamic decorating
Suppose we have a calculator class (Actually any class would do).
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_calc_class,indent=0]
----
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:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_tracing_decorator,indent=0]
----
Here is how to use the class in a script:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_tracing_decorator_usage,indent=0]
----
And here is what you would see after running this script:
----
Calling add{3, 12}
Got 15 in 31 ms
----
== Decorating with an Interceptor
The above timing example hooks into the lifecycle of Groovy objects (via `invokeMethod`). This is such an important style performing meta-programming that Groovy has special support for this style of decorating using __interceptors__.
Groovy even comes with a built-in `TracingInterceptor`. We can extend the built-in class like this:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_interceptor,indent=0]
----
Here is an example of using this new class:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_interceptor_usage,indent=0]
----
And here is the output:
----
before Calc.ctor()
after Calc.ctor()
Duration: 0 ms
before Calc.add(java.lang.Integer, java.lang.Integer)
after Calc.add(java.lang.Integer, java.lang.Integer)
Duration: 2 ms
----
== Decorating with java.lang.reflect.Proxy
If you are trying to decorate an object (i.e. just a particular instance of the class, not the class generally), then you can use Java's `java.lang.reflect.Proxy`. Groovy makes working with this easier than just Java. Below is a code sample taken out of a grails project that wraps a `java.sql.Connection` so that it's close method is a no-op:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_reflect_proxy,indent=0]
----
If there were many methods to intercept, then this approach could be modified to look up closure in a map by method name and invoke it.
== Decorating with Spring
The http://www.springframework.org[Spring Framework] allows decorators to be applied with __interceptors__ (you may have heard the terms __advice__ or __aspect__). You can leverage this mechanism from Groovy as well.
First define a class that you want to decorate (we'll also use an interface as is normal Spring practice):
Here's the interface:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_spring_calc,indent=0]
----
Here's the class:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_spring_calc_impl,indent=0]
----
Now, we define our wiring in a file called `beans.xml` as follows:
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd">
<bean id="performanceInterceptor" autowire="no"
class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor">
<property name="loggerName" value="performance"/>
</bean>
<bean id="calc" class="util.CalcImpl"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="calc"/>
<property name="interceptorNames" value="performanceInterceptor"/>
</bean>
</beans>
----
Now, our script looks like this:
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_spring_context,indent=0]
----
And when we run it, we see the results:
----
21/05/2007 23:02:35 org.springframework.aop.interceptor.PerformanceMonitorInterceptor invokeUnderTrace
FINEST: StopWatch 'util.Calc.add': running time (millis) = 16
----
You may have to adjust your `logging.properties` file for messages at log level `FINEST` to be displayed.
== Asynchronous Decorators using GPars
The following example is inspired by some of the early example code for the http://design.cs.iastate.edu/~panini/[Panini] programming language.
These days, you'll see this style used with async functions in JavaScript.
[source,groovy]
----
include::{projectdir}/src/spec/test/DesignPatternsTest.groovy[tags=decorator_gpars,indent=0]
----