Zen and Groovy's Expando
In the past, I had trouble remembering the concept behind Groovy's Expando
class. I knew it wasn't difficult but it was elusive. Perhaps I was distracted by the groovy-ness of the name.
In a recent training class with Jeff Brown, I was struck by a metaphor.
Readers of CtJ know the old refrain of dynamic-typing:
If it walks like a duck and quacks like a duck, I would call it a duck.
In other words, if "it" can provide the desired behaviour (e.g. quacking), then it can pass for a duck, in so far as a dynamic language cares. Cool beans.
Well, in Groovy, the
Expando
class is an empty "it"! It is a container for various behaviours, to which we can add stuff. Check out this code sample:See? We create "it", then add quacking behaviour, and then test it. Easy. This prints
// tests the incoming, purported "duck" to see
// if "it" really quacks
def duckTester = { duck ->
duck.quack()
}
////////////////////////// Main
// create "it"... it cannot quack -- yet
def it = new Expando()
// add quacking
it.quack = { println "quack!" }
// call test: "if it quacks like a duck"
duckTester.call(it)
quack!to the console.
Question: does anyone know the origin of the
Expando
name? I've searched but not found it.
ps. A less glamorous way of thinking about it is that
Expando
is a map of methods and attributes. I'm sticking with ducks and it.pps.
Expando
's are especially useful for mocking objects in unit-tests.
5 comments:
You can do a similar thing in Beanshell, with if I remember correctly, a This object, which serves as your context.
I found that this was incredibly useful for writing DSLs in BeanShell as you could write scripts that work on a well-known (to the script) set of attributes that are dynamically added to your current context. Presumably, you could leverage Expando in the same way.
For example, we created a set of beanshell functions that were basically a DSL for testing JDBC, so we had a function createConnection(...) that took some url/user/pw info, created a connection and stuffed it into a Connection object in context (no Connection is returned). Then other functions that executed a query (using Connection, and stuffing results into a ResultSet in context), walked forward and backward, etc.
This made testing JDBC scenarios for our custom JDBC driver really easy to write and understand.
Expando is full of schizzle, isn't it? ;)
This doesn't really have much to do with Expando but your code is calling a closure using the .call() method. If you are invoking a closure from groovy, you can simplify that by treating the closure as a method.
// invoke the call method...
duckTester.call(it)
// invoke the closure...
duckTester(it)
Thanks for the comments, gents...
@Alex. I've thought about a 'Rosetta Stone' diagram that maps out certain concepts across languages. I didn't know about Beanshell but certainly Javascript (and probably Python, Ruby) have similar behaviour. That's cool re: DSLs.
@Jeff. Thanks... Groovy never ceases to amaze me in the sense that there is always one more thing one can do to make it tighter.
For my part one and all must browse on this.
Ok, years late to say... but Expando == Expendable bean
Post a Comment