Loading...

August 29, 2009

Groovy Goodness: Delegate to Simplify Code

Groovy supports the @Delegate annotation. With this annotation we can import all the methods of the class the annotation is used for. For example if we use the delegate annotation for the Date class we get all the methods of the Date class in our class. Just like that. This is best explained with a little sample in which we use the @Delegate annotation for properties of type Date and List:

class SimpleEvent {
    @Delegate Date when
    @Delegate List<String> attendees = []
    int maxAttendees = 0
    String description
}

def event = new SimpleEvent(when: new Date() + 7, description: 'Small Groovy seminar', maxAttendees: 2)

assert 0 == event.size()  // Delegate to List.size()
assert event.after(new Date())  // Delegate to Date.after()
assert 'Small Groovy seminar' == event.description
assert 2 == event.maxAttendees

event << 'mrhaki' << 'student1'  // Delegate to List.leftShift()
assert 2 == event.size()
assert 'mrhaki' == event[0]

event -= 'student1'  // Delegate to List.minus()
assert 1 == event.size()

We have used the @Delegate annotations and as by magic the SimpleEvent has all methods of both the Date class and List interface. The code reads naturally and the meaning is obvious. Because the SimpleEvent class has all methods from the List interface we can override the methods as well. In our sample we override the add() so we can check if the number of attendees doesn't exceed the maximum number of attendees allowed:

class SimpleEvent {
    @Delegate Date when
    @Delegate List<String> attendees = []
    int maxAttendees = 0
    String description

    boolean add(Object value) {
        if (attendees.size() < maxAttendees) {
            return attendees.add(value)
        } else {
            throw new IllegalArgumentException("Maximum of ${maxAttendees} attendees exceeded.")
        }
    }
}

def event = new SimpleEvent(when: new Date() + 7, description: 'Small Groovy seminar', maxAttendees: 2)
event << 'mrhaki' << 'student1'

try {
    event << 'three is a crowd.'
    assert false
} catch (IllegalArgumentException e) {
    assert 'Maximum of 2 attendees exceeded.' == e.message
}