When dealing with OOP languages, composition over inheritance has been the preferred approach for a while now as it offers a greater flexibility in most cases. Before you start, I’m not going to get into that dispute whether one should opt for inheritance or not, enough to say that there are cases where composition is used. And that comes with its own headaches!
One of the major headaches, especially when dealing with languages such as Java, is adding delegation into the picture as it requires a lot of code to be written (duplicated) in the host class just to be able to delegate to the class you are enclosing. (This is the point where a lot of the inheritance supporters would just smirk and remind me you don’t have to do that when you are using inheritance. Let’s just ignore them for now!)
There are some IDE’s which provide tools for generating code for this but typically they do a one-off job: they generate all the methods and properties once and then if you change the class after that it’s down to you to take care and cascade any changes into the other class.
Groovy offers an awesome component to help with this: the @Delegate annotation. Simply annotate the member variable with this and Groovy will generate all the getters/setters and methods in the owner class. Even more so, it actually modifies the owner class to implement all the interfaces that the field implements and provides implementation for these interfaces in the owner class which simply just delegate the calls to the field.
What’s even nicer about this is that you can have multiple fields involved in delegation. In other words, you can create a composite class with a few fields and annotate each one of them with this and despite not writing any other code, your class will already have all the functionality of each of the fields!
Here’s a simple example of delegating to 2 separate classes — one encapsulates a person name (first and last) and the other one stores the year of birth. You put these 2 together you get a more detailed representation of a “person” which includes their names as well as year of birth:
As you can see and as to be expected, now the properties from the inner beans are now available on the
DelegationBean class. And also we should now expect that our
DelegationBean is implementing
Sortable — from the beans we are delegating to. But when we call
println (one < two) we will get an error something like this:
Exception in thread "main" groovy.lang.GroovyRuntimeException: Cannot compare delegation.DelegationBean with value 'delegation.DelegationBean@5762806e' and delegation.DelegationBean with value 'delegation.DelegationBean@17c386de' at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareToWithEqualityCheck(DefaultTypeTransformation.java:603) at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareTo(DefaultTypeTransformation.java:543) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareTo(ScriptBytecodeAdapter.java:692) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareLessThan(ScriptBytecodeAdapter.java:712) at delegation.DelegationBean.main(DelegationBean.groovy:16) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Well that sounds like our
DelegationBean class actually doesn’t implement
Sortable! Let’s verify this to be the case: replace the
println (one < two) with
assert one instanceof groovy.transform.Sortable and you will notice that when you run the code again you get:
Exception in thread "main" Assertion failed: assert one instanceof Sortable | | | false delegation.DelegationBean@12028586 at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:404) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:650) at delegation.DelegationBean.main(DelegationBean.groovy:16) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
So what do you know, our class does NOT implement
Sortable! Despite the spec for
@Delegate stating in clear:
By default, the owner class will also be modified to implement any interfaces implemented by the field. So, in the example above, because Date implements Cloneable the following will be true:
It turns out (try it out) that transformation interfaces (in
groovy.transform package) are not included in this.
So let’s change the code now and implement a
Comparable interface and handcraft the comparison ourselves in the 2 bean classes:
Now let’s change our
DelegatioBean as follows and see what happens:
If you run the above code you will see that the
assert statement doesn’t trigger any errors (therefore confirming that now our
DelegationBean DOES indeed implement automatically
Comparable). However, you get this:
Exception in thread "main" java.lang.ClassCastException: delegation.DelegationBean cannot be cast to delegation.BeanOne at delegation.DelegationBean.compareTo(DelegationBean.groovy) at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareToWithEqualityCheck(DefaultTypeTransformation.java:592) at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.compareTo(DefaultTypeTransformation.java:543) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareTo(ScriptBytecodeAdapter.java:692) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.compareLessThan(ScriptBytecodeAdapter.java:712) at delegation.DelegationBean.main(DelegationBean.groovy:21)
Well what gives? Well it turns out based on the error that our automatically provided
compareTo() method in our
DelegationBean now expects the parameter to be a …
BeanOne instance! Why? The answer is in the annotation docco again:
If multiple delegate fields are used and the same method signature occurs in more than one of the respective field types, then the delegate will be made to the first defined field having that signature. If this does occur, it might be regarded as a smell (or at least poor style) and it might be clearer to do the delegation by long hand.
Ok, so this makes sense! This basically says because we are delegating to more than one class implementing
Comparable, our class will actually delegate to the first encountered
@Delegate annotation — which in our case is
This means that if we run
println (one < two.person) we will get
true (try it!).
Also it means that in this case we need to manually craft the
DelegationBean comparison method. But in order to do that, we need to get rid of the provided implementation first. And it turns out that the
@Delegate annotation provide support for this by using
@Delegate(interfaces=false) BeanOne person @Delegate(interfaces=false) BeanTwo dateOfBirth
Now we can handcraft our own
compareTo() method in the
DelegationBean class and we’re set.
As the docco rightly points out, more than one
@Delegate per class is a bit of a code smell — stick to one delegation per class and you’ll be fine. And also remember that the
groovy.transform annotations do NOT get carried over.
More from my site