Composition and Delegation in Groovy
Using composition with Groovy? Then it's a good idea to learn the specifics of the @Delegate annotation to see where it does, and doesn't, work when generating code.
Join the DZone community and get the full member experience.
Join For FreeOne 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 IDEs that 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 two separate classes — one encapsulates a person name (first and last) and the other one stores the year of birth. You put these two together and get a more detailed representation of a “person”, which includes their names as well as year of birth:
import groovy.transform.Sortable
import groovy.transform.TupleConstructor
@Sortable
@TupleConstructor
class BeanOne {
String firstName
String familyName
public String getFullName() {
"$firstName $familyName"
}
}
import groovy.transform.Sortable
import groovy.transform.TupleConstructor
@Sortable
@TupleConstructor
class BeanTwo {
int yob
public int getAge() {
new Date().year - yob
}
}
class DelegationBean {
@Delegate
BeanOne person
@Delegate
BeanTwo dateOfBirth
static void main(String... args) {
DelegationBean one = new DelegationBean(person: new BeanOne("Liviu", "Tudor"), dateOfBirth: new BeanTwo(yob: 1975))
DelegationBean two = new DelegationBean(person: new BeanOne("William", "Shakespeare"), dateOfBirth: new BeanTwo(yob: 1564))
println one.fullName
println two.fullName
println(one < two)
}
}
As you can see, and as to be expected, 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 looking 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
clearly stating:
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 two bean classes:
package delegation
import groovy.transform.TupleConstructor
@TupleConstructor
class BeanOne implements Comparable<BeanOne> {
String firstName
String familyName
public String getFullName() {
return "$firstName $familyName"
}
@Override
int compareTo(BeanOne o) {
if (!o) return false
if (this.is(o)) return false
return fullName <=> o.fullName
}
}
package delegation
import groovy.transform.TupleConstructor
@TupleConstructor
class BeanTwo implements Comparable<BeanTwo> {
int yob
public int getAge() {
new Date().year - yob
}
@Override
int compareTo(BeanTwo o) {
if (!o) return false
if (this.is(o)) return false
return age <=> o.age
}
}
Now let’s change our DelegatioBean
as follows and see what happens:
package delegation
import groovy.transform.Sortable
class DelegationBean {
@Delegate
BeanOne person
@Delegate
BeanTwo dateOfBirth
static void main(String... args) {
DelegationBean one = new DelegationBean(person: new BeanOne("Liviu", "Tudor"), dateOfBirth: new BeanTwo(yob: 1975))
DelegationBean two = new DelegationBean(person: new BeanOne("William", "Shakespeare"), dateOfBirth: new BeanTwo(yob: 1564))
assert one instanceof Comparable
println one.fullName
println two.fullName
println(one < two)
}
}
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 BeanOne
!
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 interfaces=false
parameter:
@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.
Published at DZone with permission of Liviu Tudor, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments