Aug 132010
One of the things that Groovy is really great at is creating Domain Specific Languages (DSL) or Builders.
When creating a builder I wanted to construct the builder and pass in a closure all in one shot.
While what I was looking for can’t quiet be done (something to do with inner classes so far as I can tell):
def a = new A() { println 'Hi' // error: unexpected token: println } // While we can pass a closure as an argument this simply doesn't cut it for a DSL. def cl = { println 'Hi' } def b = new A(cl) |
Using the with or identity methods comes pretty close:
class A { def x = 2 def A() { println 'Constructor called' } } def a = new A().with { obj -> println 'Closure called' obj.x = 3 return obj } println a.x |
Output:
Constructor called Closure called 3 |
In the case of a builder the other option is to use the provided call method or provide a more domain specifically named build method:
class A { def x = 2 def A() { println 'Constructor called' } def call(Closure cl) { handleClosure(cl) } def build(Closure cl) { handleClosure(cl) return this // Must return this object not the result of the closure call. } private handleClosure(Closure cl) { cl.delegate = this cl.resolveStrategy = Closure.DELEGATE_FIRST // Allows the closure to access properties directly. cl.call() } } // Option 1, note that a{ } is equivalent to a.call() { } def a = new A() a { println 'Closure called' x = 3 } assert a.x == 3 // Option 2, a more DSL method and naturally overloads that take args are possible: build(args) { }. def b = new A().build { println 'Closure2 called' x = 4 } assert b.getClass() == A assert b.x == 4 |
Now we have the beginnings of a builder. To really give the builder some dynamic attitude you’ll want to take a look at invokeMethod, getProperty, propertyMissing and methodMissing.