Sunday, 10 Jun 2007
Annotated Callback Methods
There's a Java design pattern that shows up in Guice, TestNG, and JUnit
4 that seems common enough to need a name. I'm calling it an
annotated callback method, and I'll describe it here, in the
format popularized by the Design Patterns
book.
Pattern Name
Annotated Callback Method
Intent
Conveniently register callbacks with a library.
Motivation
Java doesn't have direct support for functions, so the typical way to
write a callback is to implement a one-method interface. This can be
done inline using anonymous inner classes. However, anonymous inner
classes are a clumsy language construct that confuses many beginners,
and it makes callback-registering code difficult to read, especially
when you need to write a large number of callbacks. If callbacks can
be written using syntax more similar to regular methods, the library
can be significantly easier to use.
Applicability
You are writing a library that requires many callbacks to be
registered, and are willing to do extra work to increase the library's
ease of use.
Participants
There is a callback provider that exports some callback methods
and a library that calls them.
Collaborations
The library declares a Java annotation:
@Retention(RetentionPolicy.RunTime)
@Target(ElementType.METHOD)
public @interface Foo {}
The callback provider puts this annotation on each method that it
wishes to export to the library:
public class FooCallbacks {
@Foo
public void methodToCall() { ... }
@Foo
public void anotherMethodToCall() { ... }
}
The callback provider somehow registers this class with the library:
something.register(FooCallbacks.class);
or
something.register(new FooCallbacks());
The library uses reflection to find the callback methods and verifies
that they have the correct parameters and return values.
Consequences
- The names of the callback methods are irrelevant to the library,
giving the person writing the callbacks the freedom to use more
meaningful names and to rename methods at any time.
- The contract between a callback method and its caller is implicit.
As a result, the Java compiler can't verify that a callback method's
parameters and return types are correct, and generic refactoring tools
can't be used to automatically change the method signature everywhere
it's used. The library can provide type-checking using an annotation
processor or at runtime, and refactoring tools could be written
explicitly supporting the library, but this increases the burden on
the library's authors.
- When you use an IDE's to find all the usages of a method, it will
not list the library as a caller. If the annotation is well-known,
this isn't a problem, but it's one more thing for people unfamiliar
with the code to learn.
- The class that provides the callbacks will have a dependency on
the annotation interface.
As a result, annotated callbacks are suitable for stable and
commonly used libraries. For internal interfaces that may change
frequently, anonymous inner classes are more appropriate.
Variations
In versions of Java before JDK 1.5, annotations were not available, so
alternate ways of identifying callback methods had to be used. For
example, JUnit 3.x has the well-known convention of starting all
callback methods that implement a test case with test. Some
other frameworks used Javadoc tags.
If annotating each method is too bothersome, a class annotation might
be used instead. In that case, the library's way of finding callback
methods needs to be documented, and it's more likely that some readers
won't understand that the method is used as a callback.
respond | link |
/code
|