Self-bounding Visitor
I came across the generics self-bounding pattern, thanks to Olivier’s blog [in french] that uses it to describe fluent extensible interfaces. After a bit of research I found a very interesting forum thread that was talking about that pattern more in depth, in particular how it is possible to extend the pattern to a couple of classes.
Let’s just start with a simple example, a binary tree class:
abstract class Node<E extends Node<E>> {
public E left;
public E right;
}
class FooNode extends Node<FooNode> {
}
class BarNode extends FooNode {
}
class JuuNode extends Node<JuuNode> {
}
With that design, we constraint the Node class to be subclasses and have its children to be a subclass of its own subclass, and now we can write:
FooNode root = new FooNode();
root.left = new FooNode();
root.right = new BarNode();
But that is not possible:
FooNode root = new FooNode();
root.left = new JuuNode();
If you have carefully read the forum thread, someone posted an interesting extension of the pattern that uses two classes.
The Reflext framework I started to write a few months ago was using a visitor pattern to visit a set of types. There are several ways to visit a set of types, one of them is to visit the class hierarchy of super classes and implemented interfaces. There are many ways to implement the visitor pattern, one of them relies on decoupling the visitor from the code (or strategy) that takes care of doing the visit. The main interest is to reuse the visiting strategy many times and avoid the visitor to care about how visit is done, the visitor only cares about the overall strategy.
Based on this assumption, we can define two interfaces named Visitor and VisitorStrategy that will vary together, as it makes sense to use a hierarchy visitor with the hierarchy visitor strategy. Let’s start with the base interfaces. We use it a Type object as visited object, in the reflext framework it’s a TypeInfo interface that represents a type (which can be either runtime reflection or APT compile time reflection).
interface Visitor<V extends Visitor<V,S>, S extends Strategy<V,S>> {
// The interface is empty as the base visitor does not provide an API
}
interface Strategy<V extends Visitor<V,S>, S extends Strategy<V,S>> {
void visit(Type type, V visitor);
}
Now we can define the hierarchy visitor and its strategy
interface HierarchyVisitor<T extends HierarchyVisitor<T>> extends
Visitor<T, HierarchyStrategy<T>> {
// The API for hierarchy visit
void enter(Type type);
void leave(Type type);
}
abstract class HierarchyStrategy<T extends HierarchyVisitor<T>> extends
Strategy<T, HierarchyStrategy<T>> {
public void visit(Type type, T visitor) {
// We only accept type which are java classes
if (type instanceof Class) {
if (accept(type)) {
// Visitor callback
enter(type);
// Now visit the super class if we have one
Class clazz = (Class)type;
Class superClazz = clazz.getSuperClass();
if (superClazz != null) {
visit(superClazz, visitor);
}
// And now visit the interfaces
for (Class itf : clazz.getInterfaces()) {
visit(itf);
}
// Visitor callback
leave(type);
}
}
}
abstract boolean accept(Type type);
}
We can see that the hierarchy visitor and its strategy vary together because they are constrained by design. The last piece missing is the to make vary the strategy (otherwise we would make the visitor and its strategy the same class…). We do it via an enum that provides several strategies
enum HierarchyScope {
ALL() {
public <T extends HierarchyVisitor<T>> HierarchyStrategy<T> get() {
return new HierarchyStrategy<T>() {
public boolean accept(Type type) {
return true;
}
}
}
},
ANCESTORS() {
public <T extends HierarchyVisitor<T>> HierarchyStrategy<T> get() {
return new VisitorStrategy<T>() {
public boolean accept(Type type) {
return (type instanceof Class) && !((Class)type).isInterface());
}
}
}
};
public abstract <T extends HierarchyVisitor<T>> HierarchyStrategy<T> get();
}
Et voilà, now we can use safely the visitor. I’m not convinced that is a great piece of software, I’m just sure that it was very fun to write and research. Without the mentioned blogs and my favorite IDE (Intellij IDEA) it would have been nearly impossible to write correctly :-).
Self-bounding generics is a curious beast, it’s even called there “That Recursive Java Generics Thing”. Have fun!
blog comments powered by Disqus