Wednesday, October 12, 2011

Dynamic dispatch with a twist

Had a silly idea for trying out Tapestry 5.3's new ASM wrapper called Plastic: what if we could implement message dispatchers in patterns like Visitor without ruining the forward compatibility of the Visitor SPI?
Let's say we have a generic message interface:
and a message dispatcher (visitor):
When implementing new MyMessage subtypes, we would create additional methods in the interface, but this would also require re-implementing all MyMessageDispatcher subtypes. Additionally, when we have a generic implementation for handling MyMessage and we do not care about the subtypes in a specific scenario, we would not want to implement every possible handle() method out there.
Let's say we have a bunch of message classes:

When creating a message handler for this tree, we might want to handle the messages like this:

One option would be to have a reflection-based check in the handle methods defined by the MyMessageDispatcher interface. Another would be to avoid reflection and add a bunch of instanceof statements in the same method, achieving some form of type-safety. The (twisted) option described here would essentially be the second solution, but with a proxy class created on-the-fly. The main requirements for the proxy are:

  1. The proxy must implement a given dispatcher interface (MyMessageDispatcher)
  2. When handle(MyMessage) is called, the proxy must:
    1. Determine the runtime class of the argument
    2. Find an implementation of the handle() method with the first parameter closest to the runtime class of the message in type hierarchy. Excluding the first argument, the handler must have the same signature and return type as the method specified by the interface
    3. Call the implementation and return the result.

Let's implement a service for creating this kind of a proxy:

The transformer responsible for implementing this proxy is:

Internally, a DispatchTarget class is used:

A dispatcher proxy can be initialized, using the createProxy() method, passing the implementation instance to the method:

I'm actually quite interested in the performance of this bytecode mutant, compared to a reflection-based approach, so there could be some optimization and performance testing waiting ahead.