Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Dynamic metaprogramming, taking reuse to the next level

DZone's Guide to

Dynamic metaprogramming, taking reuse to the next level

· Java Zone
Free Resource

Bitbucket is for the code that takes us to Mars, decodes the human genome, or drives your next car. What will your code do? Get started with Bitbucket today, it's free.

This article is about how to take reuse to the next level by using Groovy's dynamic metaprogramming capabilities.

Groovy has the option of appearing strongly typed or using dynamic typing (some may say duck typing). Now underneath the covers Groovy is always really dynamically typed. When you use strong typing in Groovy, you are much more likely to get runtime exception then you are to get a compile error.

Unlike Java, with Groovy it is easy to go to the next level of reuse by using its dynamic metaprogramming features.

Let's say for example, we had a Computer class that was using the Observer/Observable design pattern to notify interested third parties that a rent event occurred. We will show how to reuse the same code but vary the event that gets thrown. This Computer class uses/implements the Java events as follows:

RentableEvent that extends java.util.EventObject
 
public class RentableEvent extends EventObject{
RentableEvent (Computer source) {
super(source)
}
}
RentListener that extends java.util.EventListener
 
interface RentListener extends EventListener {
void rented(RentableEvent re)
void returned(RentableEvent re)
void overdue(RentableEvent re)
}

There are no Groovy goodies yet. The above is standard Java circa 1997. The listener defines three events: when a computers is rented, when a computer is returned and when a computer was returned but overdue.

Then in the Computer class we would have the following code to fire events:

Computer class that is rentable
 

public class Computer implements Serializable, Rentable {
...
/** rentableListeners List of RentListener's property that holds listeners. */
List rentableListeners = []


/** Rent the computer. */
void rent(int days=5) {
/* Throws an IllegalStateException if the computer was already rented. */
if (rented) {
throw new IllegalStateException("You can't rent a computer twice")
}
/* Set the days property, startRent and the rented property accordingly. */
this.days = days
rented = true
startRent = Calendar.getInstance()
/* Fire the rent event. */
fireRentEvent()
}

void endRent(Calendar returnedOn=Calendar.getInstance()){
/* Determine if the due date is before the returned date. */
Date dueDate = startRent.time + days
Date actualReturnDate = returnedOn.time

/* Fire the appropiate event based on the overdue status. */
if (dueDate.before (actualReturnDate)) {
fireOverdueEvent()
} else {
fireReturnedEvent()
}
rented = false
}

/* Fire rented event. */
public void fireRentedEvent() {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener -> listener.rented(event) }
}

/* Fire overdue event. */
public void fireOverdueEvent() {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener -> listener.overdue(event) }
}

/* Fire returned event. */
public void fireReturnedEvent() {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener -> listener.returned(event) }
}

void addRentableListener(RentListener listener) {
rentableListeners << listener
}

void removeRentableListener(RentListener listener) {
rentableListeners << listener
}

}

Notice that the above fires three types of events as follows: fireRentedEvent, fireOverdueEvent, and fireReturnedEvent.

For those new to Groovy, to fire an event, the above code uses Groovy's GDK method for collections called each, which will invoke the block (called a closure) for each iteration (each item) in the collection of listeners. Then inside of the block (again called a closure), we call the appropriate method to notify the listener of the event. GDK stands for Groovy Development Kit and is Groovy's way of extending Java core classes with Groovy language features.

One thing you will notice about fireRentedEvent, fireOverdueEvent, and fireReturnedEvent is that they are nearly the same. Thus the issue is we have three methods that are nearly the same. Essentially they break the Don't-repeat-yourself (DRY) principle. Let's resolve this issue using Groovy's metaprogramming capabilities.

In Groovy, it is easy to make the listener method that gets called a parameter by using invokeMethod.

Using invokeMethod to get a new level of reusability
 

/* Fire rented event. */
public void fireRentEvent(String methodName="rented") {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener ->
/* Use invokeMethod to invoke method. */
listener.invokeMethod(methodName, event)
}
}

Now which method that gets invoked is specified by the methodName and we got rid of three nearly identical methods and replaced it with one method. Imagine we had 10 events, you can easily see that the savings adds up quickly.

Now granted this article is instructive; therefore, the example it uses is a bit contrived. However there are times when you have very similar blocks of code that do nearly the same thing except for the method that is getting called and yes I know you could rewrite the above much smaller as follows:

 
/* Fire returned event. */
public void fireReturnedEvent() {
rentableListeners.each {it.returned([this] as RentableEvent) }
}

And the above is a valid point. However, the point of the article is how do you achieve reuse when you have very similar blocks of code (in this case methods) that do nearly the same thing except invoke different methods. In Java, it becomes a bit harder to get reuse. You end up writing inner classes to pass which method gets called or duplicating code or if you are dealing with a hierarchy of objects, you might implement the visitor design pattern. In Groovy, you can just use invokeMethod as an option.

This brings up another point. In Groovy, there are a few ways to easily achieve this reuse. We could have passed a method reference. We could have used a closure. In two brief follow up articles we will cover both of these approaches.

The invokeMethod method is added to all instances. It gets added to java.lang.Object. The invokeMethod method signature takes two arguments, the name of the method that you want to invoke and then n number of arguments using Java 5 style variable arguments. It is very easy to use.

Now some may have a a natural aversion to passing around strings to invoke methods. This is why follow up articles will show two ways to achieve the same thing without passing Strings. You could, however, also codify the strings by defining two short methods as follows:

Codifying strings by adding helper methods
 

/* Fire overdue event. */
public void fireOverdueEvent() {
fireRentEvent("overdue")
}

/* Fire returned event. */
public void fireReturnedEvent() {
fireRentEvent("returned")
}

A more idiomatic approach to dynamic method invocation

There is a more idiomatic approach than using invokeMethod. In Groovy, you can use instance."foo"() to execute a method called foo. Using this approach you could do the following:

rentableListeners.each {RentListener listener ->
/* Call method dynamically by name. */
listener."$methodName" event
}

The advantage of invokeMethod approach over the idiomatic approach is that it will use one less level of indirection, as the idiomatic approach uses a GString ("$methodName") that needs to be resolved to a method name. (Special thanks to Andres Almiray and Bloid for this perl of wisdom and reasoning.) The idiomatic approach is probably the grooviest way to do it. For those new to Groovy, it should be noted that you can call methods without parentheses.

Conclusion

In this short article, we showed how you can push reuse to a new level and stay extremely DRY by using Groovy's dynamic metaprogramming support. In a follow up articles, we will show how to achieve the same by using Groovy closures and then using method references.

Note that this article is not prescriptive. It does not say you should use invokeMethod, merely, that invokeMethod is a viable option to achieve another level of reuse.

If you liked this article and you are considering learning more about Groovy, please consider ArcMind's Groovy course: Groovy training. Also I plan on adding Groovy language support to the soon to be renamed Crank project. You can find the raw versions of this article here Dynamic metaprogramming, taking reuse to the next level and other articles in progress here articles in progress .

Bitbucket is the Git solution for professional teams who code with a purpose, not just as a hobby. Get started today, it's free.

Topics:

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}