问题
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 BodyPart
s are hierachical. Some BodyPart
s may contain some other BodyPart
s. 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 BodyPart
s.
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 BodyPart
s, 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 BodyPart
s 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.
回答1:
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.
来源:https://stackoverflow.com/questions/14698253/generics-overkill-in-visitor-pattern