Saturday, February 26, 2005

Reflection: type madness or ultimate flexibility

On a number of recent occasions I have found myself looking at Java code that makes extensive use of reflection. Being somewhat conservative in my ways I was initially somewhat suspicious of this technique. However over time I have come to appreciate the flexibility it provides. However to my mind it is one of the features Java provides that is most open to abuse.

Let's consider the positive side of reflection first. Using reflection I can work with classes at runtime that I don't know about at compile time. A typical application of this idea is a piece of software that supports plugins such as Eclipse. The following diagram shows the basic idea.




The plugin manager is responsible for loading plugins according to some policy (at startup, polling during runtime etc). The plugin supplier creates the plugin and some kind of descriptor identifying the class in the plugin that implements IPlugin. This is a string, so the plugin manager uses something on the lines of the following to load the plugin:


Class pluginClass = Class.forName(pluginClassName);
IPlugin plugin = pluginClass.getInstance();
... // use plugin


Note here that due to the fact that constructors can not be specified by interfaces, such an approach requires an agreement about the constructors provided by the plugin class that can not be checked by the compiler. Typically this is resolved by requiring a zero argument constructor, and then having an init method defined in IPlugin which is called immediately after instantiation. Alternatively the agreement could be for a constructor with a list of parameters of specified types, and then pluginClass.getDeclaredConstructor() would be invoked with a list of parameter types as argument. This would return an instance of java.lang.reflect.Constructor which could then be instantiated.

This use of reflection, where at application compile-time not all of the runtime classes are available, is how I think it ought to be used. So far so good. Where's the problem then?

Consider the following example, based on a little application I developed for fun recently (more of that in a later blog). Suppose you wish to use a wizard dialog to gather information in a number of stages. There is a certain amount of repetition in the structure of the dialogs (buttons for next and previous, handlers for these buttons etc) which means it makes sense to capture the commonality in an abstract superclass and the specifics in concrete subclasses - a straightforward implementation of the template method pattern. Stripping away all detail irrelevant to the discussion at hand we might have something like


public abstract class WizardDialog implements ActionListener {

public abstract void display();

public void actionPerformed(ActionEvent ev) {
if (...) // source of event ev is "next" button
{
String className = nextDialog();
if (className != null){ // null indicates the wizard has completed
Class nextClass = Class.forName(className);
WizardDialog nextDialog = (WizardDialog) nextClass.getInstance();
nextDialog.display();
}
} else if (...) // handle other events including previous button
...
}

// return class name of previous dialog
public abstract String previousDialog();

// return class name of next dialog
public abstract String nextDialog();

}


The first step in the wizard might then be implemented as follows:

public class View1 extends WizardDialog {

public void display() {
// populate dialog
}

public String previousDialog() {
return null;
}

public String nextDialog() {
return "view.View2";
}
}


This example summarizes a number of uses of reflection I have seen over the years, from relative novices to high-profile open-source projects that really ought to know better.

There are two drawbacks to using reflection in this way; both revolve around the fact that it isn't actually necessary.

The first drawback is that in general using reflection has its price in terms of performance. It's difficult to quantify this, but I have seen studies where a factor of 10 is quoted as the lower bound. In practice for this particular example I wouldn't worry too much as performance is less of an issue but in general it seems foolish to waste performance in this way.

The second drawback is the one that is more important: by using reflection in this way, type information is being voluntarily discarded. We know that the next dialog is an instance of WizardDialog, yet we willingly throw away that information and instead pass back a string. Having a background in strongly-typed functional programming languages such as Haskell and Miranda, this bothers me. By doing this we are relinquishing the automatic type analysis that the compiler is able to perform for us. This might not seem like a big deal in this kind of toy example, but in real applications that I have worked on, this is the kind of thing that leads to runtime class cast exceptions.

What is the alternative then? Well the point is that we know about all the classes we are working with at compile time, so we can just use them directly:


public abstract class WizardDialog implements ActionListener {

public abstract void display();

public void actionPerformed(ActionEvent ev) {
if (...) // source of event ev is "next" button
{
WizardDialog nextDialog = nextDialog();
if (nextDialog!= null){ // null indicates the wizard has completed
nextDialog.display();
}
} else if (...) // handle other events including previous button
...
}

// return instance of previous dialog
public abstract WizardDialog previousDialog();

// return instance of next dialog
public abstract WizardDialog nextDialog();

}

public class View1 extends WizardDialog {

public void display() {
// populate dialog
}

public WizardDialog previousDialog() {
return null;
}

public WizardDialog nextDialog() {
return new view.View2();
}
}


Given the simplicity of this solution, how is it that I ended up messing around with reflection in the first place? Well, laziness, normally the friend of the developer, was in this case the villain. I am using the value returned by nextDialog and previousDialog for two purposes: to identify the dialog, and to indicate (by returning null or an object) whether there is a next (or previous) dialog. This latter use is exploited by the code that displays the buttons: if there is no next dialog, then the button is labelled "Finish" otherwise it is labelled "Next". Similarly the previous button is labelled "Cancel" if there is no previous dialog. I wanted to avoid creating unnecessary objects each time I invoked nextDialog() and previousDialog(). However in this case my laziness led to a poor solution; if that was my concern I should have used a singleton pattern or cached an object reference, rather than discarding type information and resorting to strings and reflection. Still, at least my laziness gave me something to blog about!

No comments: