私は、10年前に作成された古いJava1.2コードをJava7に変換するプロジェクトに取り組んでいます。そのプロジェクトは、特定の訪問者を多用します。概念的に単純にするために、訪問者は次のようなものであるとしましょう。
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.
}
クラスHead、、、、などはすべて、次のような抽象クラスのサブクラスNeckです。ShoulderArmBodyPart
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);
}
}
これらBodyPartは階層的です。一部BodyPartのは他のを含む場合がありますBodyPart。ただし、他のものが含まれている場合もあります。
その訪問者にはいくつかの非常に明確な実装があり、予想どおり、コードはキャストで機能していません。私はジェネリックを使おうとしました:
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.
}
しかし、これは機能しません。アプリケーションは、さまざまなタイプのパラメーターを渡し、同じ訪問者のメソッドのグループごとに異なるリターンを期待します。だから、私はそのようなもので終わりました:
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.
}
これは単にジェネリックをばかげたやり過ぎにし、インターフェースを非常に使いにくくします。
インターフェイスをサブインターフェイスに分割し、同じパラメータと同じリターンタイプを期待し、いくつかの場所で機能するメソッドをグループ化しようとしましたが、欠点はaccept、クラスからメソッドを削除して、BodyPart類似したsをグループ化するサブクラスにすることBodyPartでした。
次に、大きな失敗に見舞われました。ある特定の訪問者の実装には、メソッドをBodyPart呼び出す-typedパラメーターを持つacceptメソッドがありました。私はもうacceptスーパークラスにいなかったので、それは明らかにそれをするのに悪い方法でした。
ビジターのパラメーターとリターンタイプと同様に、ビジターのさまざまな実装は大きく異なります。パラメータとリターンタイプがBodyPartsである場合もあれVoidば、である場合もStringありInteger、である場合もあり、swingコンポーネントである場合もあり、その他の無関係なオブジェクトである場合もあります。ただし、各訪問者で、同様のを訪問するメソッドBodyPartは、同様のパラメーターと戻り値タイプを取得する傾向があります。
クライアントコードは常にinだけを呼び出しaccept、Headそれ以上は呼び出しません。他のすべてのacceptメソッドは、訪問者からそれ自体に呼び出されます。
ジェネリックスオーバーキルに変換せずに、そのインターフェイスをジェネリック化するにはどうすればよいですか?今instanceofのところ、プレーンを探していたメソッドに多くのを追加したBodyPartだけです。これは、ビジターパターンを使用するという全体的なポイントを単純に打ち負かします。