Polymorphism by Closure Injection? September 4, 2008
Posted by WarpedJavaGuy in java, programming.Tags: closures
6 comments
Instead of defining many classes to do the same thing differently through polymorphism, you could define just one class and achieve the same through closure injection!
I recently downloaded the feature complete BGGA closures prototype and had a bit of a play. My first experiment was to refactor an existing codebase and replace all anonymous inner classes with closures. The ones that extended single abstract method (SAM) interfaces were easy to convert. The ones that extended multiple method (non-SAM) types were a bit trickier. In the end though, I managed to successfully convert all of them, and with very little effort.
In converting the non-SAM types, I discovered that the functional equivalent of polymorphism can be achieved by injecting closures into a single concrete implementation having no subclasses.
The codebase I refactored was a small (fictitious) railroad application that provided customers with information about routes. In particular, it calculated things like the number of routes between two cities, the number of routes that do not exceed a given number of stops, the distances of individual routes, and the like. It did this by deriving routes on the fly from a given set of legs and dynamically yielding only those that match a given predicate (or condition).
Here is the original predicate code using anonymous inner classes:
public abstract class Predicate<T> {
public boolean eligible(T item) {
return yield(item);
}
public abstract boolean yield(T item);
/* Pre-defined predicates follow */
public static Predicate<Route> routesWithMaxStops(final int maxStops) {
return new Predicate<Route>() {
public boolean yield(Route route) {
return route.getLegs().size() <= maxStops;
}
};
}
public static Predicate<Route> routesWithStops(final int stops) {
return new Predicate<Route>() {
public boolean eligible(Route route) {
return route.getLegs().size() <= stops;
}
public boolean yield(Route route) {
return route.getLegs().size() == stops;
}
};
}
}
In the above code only two predefined predicates are shown for brevity (the actual code has got more).
Note that the predicate has two methods. Only routes for which both methods return true are yielded.
- eligible – determines if a route satisfies a boundary condition
- yield – determines if a route should be yielded
Here is the converted closure equivalent:
public class Predicate<T> {
private {T => boolean} eligible;
private {T => boolean} yield;
public Predicate({T => boolean} yield) {
this(yield, yield);
}
public Predicate({T => boolean} eligible, {T => boolean} yield) {
this.eligible = eligible;
this.yield = yield;
}
public boolean eligible(T item) {
return eligible.invoke(item);
}
public boolean yield(T item) {
return yield.invoke(item);
}
/* Pre-defined predicates follow */
public static Predicate<Route> routesWithMaxStops(int maxStops) {
return new Predicate<Route>(
{Route route => route.getLegs().size() <= maxStops});
}
public static Predicate<Route> routesWithStops(int stops) {
return new Predicate<Route>(
{Route route => route.getLegs().size() <= stops},
{Route route => route.getLegs().size() == stops});
}
}
If you look carefully, you will notice that both the original and converted code produce a redundant invocation when the eligible and yield expressions are identical. Fortunately, this is a flaw that can be very easily corrected in the closure version.
A possible correction is shown below (see lines 5, 17, 18, and 22):
public class Predicate<T> {
private {T => boolean} eligible;
private {T => boolean} yield;
private boolean isEligible;
public Predicate({T => boolean} yield) {
this(yield, yield);
}
public Predicate({T => boolean} eligible, {T => boolean} yield) {
this.eligible = eligible;
this.yield = yield;
}
public boolean eligible(T item) {
isEligible = eligible.invoke(item);
return isEligible;
}
public boolean yield(T item) {
return (isEligible && eligible == yield) ? true : yield.invoke(item);
}
/* Unchanged pre-defined predicates not shown here */
}
Applying the same correction to the original closureless version is not nearly as simple. It would require a more complex solution and could even involve changing the interface.
It is very interesting that polymorphic functionality can be achieved by injecting closures into one concrete class instead of defining multiple subclasses of an abstract class. Closures and anonymous inner classes, injection and inheritance, deferred execution and late binding. So many great choices!