3

I am working on a project where I am converting an old java 1.2 code written ten years ago to java 7. That project heavily (over)uses a particular visitor. To keep things conceptually simple, lets say that the visitor is something like this:

public interface RobotVisitor {
    public Object visitHead(Head p, Object arg);
    public Object visitNeck(Neck p, Object arg);
    public Object visitShoulder(Shoulder p, Object arg);
    public Object visitArm(Arm p, Object arg);
    public Object visitHand(Hand p, Object arg);
    public Object visitFinger(Finger p, Object arg);
    public Object visitChest(Chest p, Object arg);
    public Object visitLeg(Leg p, Object arg);
    public Object visitFoot(Foot p, Object arg);
    public Object visitToe(Toe p, Object arg);
    // A lot of methods.
}

The classes Head, Neck, Shoulder, Arm, etc are all subclasses of a BodyPart abstract class that is something like this:

public abstract class BodyPart {
    // A lot of fields, so it is not simple to convert this to an interface.

    public abstract Object accept(RobotVisitor visitor, Object arg);
}
// All the subclasses of BodyPart are implemented like this:
public class Arm extends BodyPart {
    // Some fields, getters and setters...

    public Object accept(RobotVisitor visitor, Object arg) {
        return visitor.visitArm(this, arg);
    }
}

These BodyParts are hierachical. Some BodyParts may contain some other BodyParts. But they sometimes might contain something else.

There are several very distinct implementations of that visitor, and as expected, the code is crippled with casts. I tried to use generics:

public interface RobotVisitor<R, E> {
    public R visitHead(Head p, E arg);
    public R visitNeck(Neck p, E arg);
    public R visitShoulder(Shoulder p, E arg);
    public R visitArm(Arm p, E arg);
    public R visitHand(Hand p, E arg);
    public R visitFinger(Finger p, E arg);
    public R visitChest(Chest p, E arg);
    public R visitLeg(Leg p, E arg);
    public R visitFoot(Foot p, E arg);
    public R visitToe(Toe p, E arg);
    // A lot of methods.
}

But this does not works. The application passes parameters from different types and expects different returns for each group of methods in a same visitor. So, I ended with something like that:

public interface RobotVisitor<A, B, C, D, E, F, G, H, I> {
    public A visitHead(Head p, B arg);
    public A visitNeck(Neck p, B arg);
    public A visitShoulder(Shoulder p, C arg);
    public A visitArm(Arm p, C arg);
    public A visitHand(Hand p, C arg);
    public D visitFinger(Finger p, E arg);
    public F visitChest(Chest p, B arg);
    public A visitLeg(Leg p, G arg);
    public A visitFoot(Foot p, G arg);
    public H visitToe(Toe p, I arg);
    // A lot of methods.
}

This simply makes the generics a ridiculous overkill, making the interface very hard to use.

I tried to divide the interface in subinterfaces, grouping methods that expects the same parameter and the same return type and that worked in some places, but the downside was to remove the accept method from the BodyPart class to subclasses that groups similar BodyParts.

Then, I hit a big failure, there was one particular visitor implementation that had a method with a BodyPart-typed parameter calling the accept method in it. Since I no more had the accept in the superclass it was clearly the bad way to do it.

The different implementations of the visitors are greatly varied, as are the parameters and return types in the visitor. Sometimes the parameters and return types are BodyParts, sometimes are Void, sometimes are String and Integer, sometimes are swing components and sometimes are other unrelated objects. However in each visitor, methods visiting similar BodyParts have a tendency to get similar parameters and return types.

The client code always calls just the accept in the Head and nothing more. All the other accept methods are called from the visitor to itself.

What should I do to try to make that interface generic without transforming it in a generics-overkill? For now I simply added a lot of instanceof's in the methods that were looking for a plain BodyPart, which simply defeats the entire point of using the visitor pattern.

  • Do the various `visitPiece` methods actually do something different or they just visit? And what are arguments and returner types of the visitor? BodyParts? Or whatever type? – Jack Feb 05 '13 at 00:38
  • Is this code using for testing? I don't see the purpose of creating a bunch of visitBodyPart() methods. – styfle Feb 05 '13 at 00:44
  • What does a BodyPart subclass .accept() method look like? I'm curious how you define the generic types in there... – sharakan Feb 05 '13 at 14:56
  • 1) Are `BodyPart`s hierarchical? i.e., does a `BodyPart` object contain other `BodyPart`s? 2) Do different visitor implementations actually have different parameter and return types for a particular visit method? 3) What does the client code that actually calls `accept` look like? If `accept` isn't also generic, how can its client code know which return type it will get? – matts Feb 05 '13 at 17:10
  • People, I edited the question to clarify your questions. – Victor Stafusa - BozoNaCadeia Feb 08 '13 at 01:26
  • I think the main problem is the variety of the different datatypes. If it is difficult to standardize, you should leave that. – gaborsch Feb 08 '13 at 01:30
  • 2
    One thing you can try: create a `VisitorReturnType` class, where you can list all different return types as attributes. In the parent `accept` method you call the getter for the proper type. IMHO this is the only possibility to have a *kind of* standard way for handling the acceptors. Similarly, you can introduce a `VisitorParameterType` class for the input parameters. – gaborsch Feb 08 '13 at 01:34
  • One thing that I am curious about your original design: Normally the visiting method in a visitor doesn't returns value. The idea for a visitor pattern, afaik, is a Visitor will have its visiting methods invoked in order of the structure of data being visited (normally controlled by the data structure). We do not need the Visitor the return the result to the data structure (as what u r doing now). For example, a visitor may keep an internal state and each visiting method will change the internal state accordingly, and at the end, we can get the visitor-specific result from the visitor. – Adrian Shum Feb 08 '13 at 03:28
  • So, the overall code sample just doesn't seems right to me, and I can hardly understand the reason behind the return value, and hence, fail to give a reasonable way to solve the problem. – Adrian Shum Feb 08 '13 at 03:29
  • @AdrianShum As I had said, the original code is something ten years old. It was wrote by someone that I don't even know. But today, I am needing to refactor it. The reason for the return value is to give information from one `visitFooBodyPart` to be used as a partial result in the `visitBarBodyPart`. In the end, the final result is what the `visitHead` returns. In some visitor, it returns subtrees to form a complete tree. In other visitor it is numbers and strings used for internal data processing. Another visitor always has `Void` as the return (was `null` before my refactorings). – Victor Stafusa - BozoNaCadeia Feb 08 '13 at 07:35
  • @GaborSch Could you write an answer explaining it in details? You may add whatever attributes and methods that you might like in the `BodyPart` class and subclasses to show your point. – Victor Stafusa - BozoNaCadeia Feb 08 '13 at 07:50
  • 2
    as much as porting old code is boring as hell, i think you are putting too much time in here. what do you gain from adding generics? what is wrong with the casts on the old code? what is the objective of the project (update to JDK7 or rewrite?)? if you go ahead and restructure too much you'll just create more bugs (as you surely know, and as an SO founder once opined: http://www.joelonsoftware.com/articles/fog0000000069.html ) – Tom Carchrae Feb 08 '13 at 07:55
  • Just curious, why should the return type be generic? From Java 5 you can specialize in a subclass for the return type of the method (i.e., if `A1 extends A` then `visitHead()` may return that in `RobotVisitor`), and you clearly don't want to return `Object` from these methods. – rlegendi Feb 08 '13 at 07:56
  • Check what GaborSch mentioned. What are the contents of the return type and the parameters. – Luis Feb 08 '13 at 08:52
  • I second Tom's thought... why is there anything to "port"? A Java 2 app should run just fine on Java 7. Now if you want to *refactor* the code, that's different, but do it incrementally and only where it actually adds value. – Lawrence Dol Feb 08 '13 at 08:58

1 Answers1

2

If you really want to refactor, my recommendations would be like this:

Use a data container type for passing parameters and return values. In my comment I recommended to use a VisitorParameterType and a VisitorReturnType, but since there is much overlapping, you can use one common datatype.

public class VisitorData {
    private A a;
    private B b;
    private C c;
    private D d;
    // one constructor for each type
    private VisitorData(A a) {
        this.a = a;
    }
    // getters, setters
}

The Visitor:

public interface RobotVisitor {
public VisitorData visitHead(Head p, VisitorData arg);
public VisitorData visitNeck(Neck p, VisitorData arg);
public VisitorData visitShoulder(Shoulder p, VisitorData arg);
public VisitorData visitArm(Arm p, VisitorData arg);
....
// A lot of methods.
}

The base class:

public abstract class BodyPart {
// A lot of fields, so it is not simple to convert this to an interface.

public abstract VisitorData accept(RobotVisitor visitor, VisitorData arg);
}

One subclass:

public class Arm extends BodyPart {
    // Some fields, getters and setters...

    public VisitorData accept(RobotVisitor visitor, VisitorData arg) {
        return visitor.visitArm(this, arg);
    }
}

The main achievement with this was not the introduction of generics, but to refactor your code to implement a uniform visitor pattern, which is much easier to follow. Also, you got rid of the nasty unchecked castings.

gaborsch
  • 15,408
  • 6
  • 37
  • 48