-
Get a monthly update on best practices for delivering successful software.

One of the new features of Groovy 1.6 includes support for per-instance metaclasses. This is a nice addition to the language, since it allows to change the behavior of objects without affecting all instances of the class as a whole.
For testing, it is often useful to mock out specific behavior of an object without affecting the class as a whole. While some mocking frameworks already allow this, they usually run through a few hoops (i.e. "partial mocks"). Allowing support directly in the language can simplify this kind of approach.
Another case where per-instance metaprogramming can be useful is in employing template patterns on a per-instance basis. Picture the case where the domain decides that one of its children is no longer in a valid state. Most domain models handle this by embedding some sort of state object within the children, so that any object holding a reference to the children know what the child can or can not do. With a per-instance metaclass, however, the aggregate root can simply add or replace behavior of that instance of the object alone.
Here's a made-up example of how this could work. Picture an online booking engine where a 'Reservation' handles the domain logic for booking a flight. Part of this may involve reserving a seat through an external airline booking system.
//
// Representation of an airline booking system
//
class ReservationService {
def name
def reserveSeat(flightNumber) {
/* some network call.. */
return true
}
def postProcess() { }
}
class Reservation {
def bookFlight(flightNumber, service) {
def confirm = service.reserveSeat(flightNumber)
if (!confirm) {
service.metaClass.postProcess = {
println "Error for provider '${service.name}'"
}
}
service.postProcess()
return confirm
}
}
reservation = new Reservation()
delta = new ReservationService(name: "Delta")
united = new ReservationService(name: "United")
assert true == reservation.bookFlight("1234", delta)
assert true == reservation.bookFlight("1234", united)
// Mock out our connection to Delta
delta.metaClass.reserveSeat = { return false }
// The following prints an error, due to 'postProcess'
assert false == reservation.bookFlight("1234", delta)
Clearly this is a made-up example, but as you see from the above code, we have a situation here where we can associate specialized behavior with a specific object at runtime. I imagine you can use the same approach with things like exceptions which can themselves be called upon to report or behave in certain ways as they are caught further up the stack. Whether or not this is a good design approach is a separate issue however.
Related posts:
Topics: Groovy, metaprogramming
This is probably a stupid question, but how does this differ from using closures:
//
// Representation of an airline booking system
//
class ReservationService {
def name
def reserveSeat(flightNumber) {
/* some network call.. */
return true
}
def postProcess() { }
}
class Reservation {
def bookFlight(flightNumber, service) {
def confirm = service.reserveSeat(flightNumber)
if (!confirm) {
service.metaClass.postProcess = {
println “Error for provider ‘${service.name}’”
}
}
service.postProcess()
return confirm
}
}
reservation = new Reservation()
delta = new ReservationService(name: “Delta”)
united = new ReservationService(name: “United”)
assert true == reservation.bookFlight(”1234″, delta)
assert true == reservation.bookFlight(”1234″, united)
// Mock out our connection to Delta
delta.metaClass.reserveSeat = { return false }
// The following prints an error, due to ‘postProcess’
assert false == reservation.bookFlight(”1234″, delta)
Comment by Tim Yates, Wednesday, February 25, 2009 @ 5:28 pm
Sorry…that was your code… (badly formatted)…this is the closure code:
//
// Representation of an airline booking system
//
class ReservationService {
def name
def reserveSeat = { flightNumber ->
/* some network call.. */
return true
}
def postProcess = { }
}
class Reservation {
def bookFlight(flightNumber, service) {
def confirm = service.reserveSeat(flightNumber)
if (!confirm) {
service.postProcess = {
println “Error for provider ‘${service.name}’”
}
}
service.postProcess()
return confirm
}
}
reservation = new Reservation()
delta = new ReservationService(name: “Delta”)
united = new ReservationService(name: “United”)
assert true == reservation.bookFlight(”1234″, delta)
assert true == reservation.bookFlight(”1234″, united)
// Mock out our connection to Delta
delta.reserveSeat = { return false }
// The following prints an error, due to ‘postProcess’
assert false == reservation.bookFlight(”1234″, delta)
Comment by Tim Yates, Wednesday, February 25, 2009 @ 5:29 pm
Hi Tim– not a stupid question at all! Effectively you get the same behavior between my example and yours in pure Groovy code. One difference I spot though is that, while the metaclass approach will still work as expected in a Java world, you can’t invoke a closure the same way as a method in Java.
If, say, you were working in a mixed environment, and a Java developer needed to reference your Groovy classes, they could still reference that ‘postProcess()’ as follows:
// Java code
new ReservationService().postProcess();
If using closures, this would *not* compile (the closure is not the same as a method). Instead, you would have to invoke the closure as follows:
import groovy.lang.Closure;
/* … */
// Java code
ReservationService r = new ReservationService();
Closure postProcessor = (Closure)r.getPostProcess();
postProcessor.call();
So the metaclass method can be changed from Groovy in the original example, and Java code can still reference it as a method. With closures, you need to be aware of the fact that you have exposed Closures if your code is running, say, within an existing Java framework.
This might not be a huge factor if you are only running in a Groovy environment such as Grails (I use closures a lot like that anyway), but the distinction still remains.
Comment by Ivan Moscoso, Wednesday, February 25, 2009 @ 11:38 pm
Just a small remark: per-instance MetaClass already existed and worked for POGOs. The “new feature” is that we can now also do the same with POJOs.
Comment by Guillaume Laforge, Thursday, February 26, 2009 @ 2:54 am
Ahhh cool! Thanks for the reply! It was the example that confused me
Keep up the Groovy posts, it’s sometimes hard to work out what’s new (and why it’s useful) from the release notes
Thanks again!
Comment by Tim Yates, Thursday, February 26, 2009 @ 3:03 am