Alex Miller lives in St. Louis. He writes code for a living and currently work for Terracotta Tech on the Terracotta open-source Java clustering product. Prior to Terracotta he worked at BEA Systems and was Chief Architect at MetaMatrix. His main language for the last decade has been Java, although Alex have been paid to program in several languages over the years (C++, Python, Pascal, etc). Alex has posted 43 posts at DZone. You can read more from them at their website. View Full User Profile

Dynamic visitor builder with closures

03.16.2008
| 4527 views |
  • submit to reddit

In my previous post I was fumbling with how to leverage closures to improve the visitor pattern. Neal suggested that I could leverage closures in building the visitor rather than executing it.

So, here's a hack at doing that. The Visitor part is all standard. [I'm taking the easy route of encoding navigation in the data structure and yes I know there are better ways but this is easier to show.]

public interface Visitable {  void accept(Visitor visitor);  }
public interface Node extends Visitable {}

public class ConcreteNode implements Node {
public void accept(Visitor visitor) { visitor.visit(this); }
}

public class CompositeNode implements Node {
public void accept(Visitor visitor) {
visitor.visit(this);
for(Node node : nodes) {
node.accept(visitor);
}
}
}

public interface Visitor {
void visit(ConcreteNode node);
void visit(CompositeNode node);
}

We then can create a VisitorBuilder which is just a builder to create DynamicVisitor instances:

public class VisitorBuilder {
public static VisitorBuilder visitor() {
return new VisitorBuilder();
}

private DynamicVisitor visitor = new DynamicVisitor();

public VisitorBuilder handleConcreteNode({ConcreteNode=>void} block) {
visitor.addConcreteNode(block);
return this;
}

public VisitorBuilder handleCompositeNode({CompositeNode=>void} block) {
visitor.addCompositeNode(block);
return this;
}

public Visitor build() {
return this.visitor;
}
}

public class DynamicVisitor implements Visitor {
private {ConcreteNode=>void} concreteNodeBlock;
private {CompositeNode=>void} compositeNodeBlock;

public void addConcreteNode({ConcreteNode=>void} block) {
this.concreteNodeBlock = block;
}

public void addCompositeNode({CompositeNode=>void} block) {
this.compositeNodeBlock = block;
}

public void visit(ConcreteNode node) {
if(concreteNodeBlock != null) {
concreteNodeBlock.invoke(node);
}
}

public void visit(CompositeNode node) {
if(compositeNodeBlock != null) {
compositeNodeBlock.invoke(node);
}
}
}

You then can dynamically assemble a visitor from blocks by doing something like:

Visitor visitor =
VisitorBuilder.visitor()
.handleConcreteNode({ConcreteNode node=>
System.out.println("Visiting ConcreteNode");
})
.handleCompositeNode({CompositeNode node=>
System.out.println("Visiting CompositeNode");
})
.build();

Node root = ...
root.acceptVisitor(visitor);

You'll note that this kind of sucks in the naming of the handle and add methods. Due to type erasure, all of the blocks passed to these methods have the same type signature so I can't overload the handle() and add() methods. I also don't know of any reflective interface on the block that would let me use reflection to discover the parameter type of the closure (also missing due to erasure I would think).

One alternative would be to have a single handle() method on the builder that took a class as an extra parameter and then encoded a big gross if/else for all possible types within the handle method. In the fluent style you could then have something like:

Visitor visitor =
VisitorBuilder.visitor()
.on(ConcreteNode.class).do({ConcreteNode node=>
System.out.println("Visiting ConcreteNode");
})
.on(CompositeNode.class).do({CompositeNode node=>
System.out.println("Visiting CompositeNode");
})
.build();

There are probably some interesting extensions to this that are model-specific. If you had many nodes that shared some common attribute, you could have methods on the builder that could apply the closure to a set of node types in one call. That sort of thing could let you build some kinds of visitors in a lot less code.

Another nice consequence is that since the closures can reach the local state during build time, it is easy to have the closures update local variables, such as by adding to a collection. I believe there are also some nice improvements for the cases of return values and exception handling. I'll play with those next.

Published at DZone with permission of its author, Alex Miller.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Soylent Green replied on Sun, 2008/03/23 - 6:10am

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Nice approach. As you said your going to further fumble around with this soon, maybe you could give us a more concrete example that in the end has a real result.

I would suggest the follwoing task: Use a Building and it's parts (stairs, windows, doors, rooms) as real world objects for your nodes hierarchie. Implement a visitor that counts all steps of all stairs and which I could then ask "int getStepsCount()" for instance.

++

Hm, btw - Why don't we get rid of visitor-double-dispatch in java 7 anyway? could we not just add/introduce a new kind of method-sematics which adds runtime-type method-dispatching instead of compiletime-type method-dispatching?

We coud just add a new keyword "runtime" to use in a way like "public void accept(final runtime Node::Stair inStair)" and "public void accept(final runtime Node::Door inDoor)" with a sematics like - if a client calls Method accept with a compile-time type of Node which has a runtime-type of Stair (Door respectively) then just dispatch to the specific method. It would be due to the compiler to require a programmer to implement a "default" Method whenever there's a runtime dispatch Method, to which a method call could be dispatched in case there's no specific method for any of the given runtime-types...

What a crazy idea...There must have been some kind of hallucinogen stuff in my coffee this morning. I see closures, I see no more checked exceptions, I see runtime-type dispatching, I see Java 7 that is no longer java...(sorry for the sarcasm, everything up to "++" is really meant as a constructive request ;-)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (MingW32) - WinPT 1.2.0

iQEVAwUBR+Y6NeDPAc7sqweYAQLveAgAr95QUwVo4bWlFo0bDvOJEJcP5r06kdlr
JtEzXl8kPb6zb948S0G+In1oWWw1c7Kx6rgFVwdDB2b/jaE4/i6FrERTDZz7D1Iu
56OwsvnmALw9CKbzW4qK9Wh/02KiD5Gxeq1UxT//IlwO6DCQ8XWcoLrjkxGyK/vl
0gEsZb7mxqPEiUoi1+XyyEoFtD+m4uXb+OEPvHSBTSIOFlUgt/V94uH03xDoMq9P
fqTDxrODF551rvI3RIxiRTTBb2QvhKcMYV6bx3ofWTtqCVdaY2IM1vSgfnZZj03X
fgY6LBs0p4cvf95Ib77eGdP7pxp6GSf4up+zogIrIqEd2QLkWSyPLQ==
=XX6g
-----END PGP SIGNATURE-----

 

 

Alex Miller replied on Mon, 2008/03/24 - 10:10pm

I think those are generally known as Multi-Methods and are available in a few languages.  Can't say I've seen anyone even considering adding them to Java 7.  Maybe you can be the first. :)

Hopefully I'll have some time soon to continue playing with this!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.