Java 如何使用双重分派来分析图形原语的交集?

Java 如何使用双重分派来分析图形原语的交集?,java,graphics,double-dispatch,Java,Graphics,Double Dispatch,我正在分析图形原语(rect、line、circle等)的交互作用,并计算重叠、相对方向、合并等。这是双重分派(例如 自适应碰撞算法通常要求 不同的对象可以用不同的方式处理。一个典型的例子是 在一个游戏环境中,宇宙飞船和宇宙飞船之间的碰撞 小行星的计算不同于 宇宙飞船和空间站 但是我还不明白主要的解释,我也不太明白答案 我当前的代码(Java)使用超类形状,类似于: for (int i = 0; i < shapes.size() - 1; i++) { for (int j =

我正在分析图形原语(rect、line、circle等)的交互作用,并计算重叠、相对方向、合并等。这是双重分派(例如

自适应碰撞算法通常要求 不同的对象可以用不同的方式处理。一个典型的例子是 在一个游戏环境中,宇宙飞船和宇宙飞船之间的碰撞 小行星的计算不同于 宇宙飞船和空间站

但是我还不明白主要的解释,我也不太明白答案

我当前的代码(Java)使用超类形状,类似于:

for (int i = 0; i < shapes.size() - 1; i++) {
    for (int j = i + 1; j < shapes.size(); j++) {
        Shape shape = shapes.get(i).intersectionWith(shapes.get(j));
    }
}
无论如何,我必须编写所有的
n*(n-1)/2
方法(并且已经这样做了)。我还必须有可扩展的代码,以便在以后适应(比如):

        } else if (shape instanceof Circle) {
            return this.intersection((Circle)shape);
我不知道如何使用double dispatch模式,也不知道double dispatch模式的价值,我希望能给出一个使用Java图形原语或类似伪cde的具体示例

更新:我已经接受@Flavio,因为(我认为)它回答了所问的确切问题。然而,我实际上实现了@Slanec,因为它解决了我的问题,而且(对我来说)更简单、更容易阅读。我有一个附属问题“解决方案是否依赖于对称关系?”

“A与B相交”通常与“B与A相交”相同,但“A与B碰撞”并不总是与“B与A碰撞”相同。(A==汽车,B==骑自行车的)。可以想象,我的交点在未来可能是不对称的(例如,“Rect部分遮挡圆”是不对称的,可能具有不同的语义


@Flavio很好地解决了维护问题,并指出编译器可以检查问题。@Slanec通过反射来检查问题,这似乎是一个有用的维护辅助工具-我不知道性能的影响是什么。

我认为是这样的:

import java.util.ArrayList;
import java.util.List;


public class DoubleDispatchTest {


    public static void main(String[] args) {
        List<Shape> shapes = new ArrayList<Shape>();
        shapes.add(new Line());
        shapes.add(new Circle());
        shapes.add(new Rect());

        for (int i = 0; i < shapes.size() - 1; i++) {
            for (int j = i + 1; j < shapes.size(); j++) {
                Shape shape = shapes.get(i).intersection(shapes.get(j));
            }
        }

    }

    abstract static class Shape {
        abstract Shape intersection(Shape shape);
        abstract Shape intersection(Line line);
        abstract Shape intersection(Circle line);
        abstract Shape intersection(Rect line);
    }

    static class Line extends Shape {
        Shape intersection(Shape shape) {
            return shape.intersection(this);
        }

        Shape intersection(Line line) {
            System.out.println("line + line");
            return null;
        }

        Shape intersection(Circle circle) {
            System.out.println("line + circle");
            return null;
        }

        Shape intersection(Rect rect) {
            System.out.println("line + rect");
            return null;
        }
    }

    static class Circle extends Shape {
        Shape intersection(Shape shape) {
            return shape.intersection(this);
        }

        Shape intersection(Line line) {
            System.out.println("circle + line");
            return null;
        }

        Shape intersection(Circle circle) {
            System.out.println("circle + circle");
            return null;
        }

        Shape intersection(Rect rect) {
            System.out.println("circle + rect");
            return null;
        }
    }

    static class Rect extends Shape {
        Shape intersection(Shape shape) {
            return shape.intersection(this);
        }

        Shape intersection(Line line) {
            System.out.println("rect + line");
            return null;
        }

        Shape intersection(Circle circle) {
            System.out.println("rect + circle");
            return null;
        }

        Shape intersection(Rect rect) {
            System.out.println("rect + rect");
            return null;
        }
    }
}

免责声明:我对双重分派不太熟悉。我看过,也看过wiki文章,但仅此而已。我只是尽我所能解决这个问题


地狱的
实例
我们可以利用关于两个相交的
Shape
对象的类信息在运行时是已知的。运行代码的
Rect
知道它是
Rect
,而
Shape
参数的类型是
Shape
,但在其上运行方法时,它将调用正确重写的concre版本te
形状
类型

在下面的代码中,将对正确的
Shape
类型调用正确的
intersect()
重载:

public interface Shape {
    public Shape intersect(Shape shape);
    public Shape intersect(Line line);
    public Shape intersect(Rect rect);
}

public class Line implements Shape {
    @Override
    public Shape intersect(Shape shape) {
        return shape.intersect(this);
    }

    @Override
    public Shape intersect(Line line) {
        System.out.println("Line - Line");
        return null;
    }

    @Override
    public Shape intersect(Rect rect) {
        System.out.println("Line - Rect");
        return null;
    }
}
public Shape intersect(Shape-Shape);
的通用实现必须复制粘贴到所有实现类中。如果您试图将
Shape
接口更改为
抽象类,并在其中包含该方法,则该方法将不起作用,因为该方法将递归调用自身:

public abstract class Shape {
    public final Shape intersect(Shape shape) {
        return shape.intersect(this);
    }
    public abstract Shape intersect(Line line);
    public abstract Shape intersect(Rect rect);
}
但是,您可以使用反射来完成:

public abstract class Shape {
    public final Shape intersect(Shape shape) {
        try {
            Method intersect = this.getClass().getMethod("intersect", shape.getClass());
            return (Shape)intersect.invoke(this, shape);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public abstract Shape intersect(Line line);
    public abstract Shape intersect(Rect rect);
}

您可以通过
Visitor
模式在Java中实现双重分派

public interface ShapeVisitor<P, R> { 
    R visitRect(Rect rect, P param);
    R visitLine(Line line, P param);
    R visitText(Text text, P param);
}

public interface Shape {
    <P, R> R accept(P param, ShapeVisitor<? super P, ? extends R> visitor);
    Shape intersectionWith(Shape shape);
}

public class Rect implements Shape {

    public <P, R> R accept(P param, ShapeVisitor<? super P, ? extends R> visitor) {
        return visitor.visitRect(this, param);
    }

    public Shape intersectionWith(Shape shape) {
        return shape.accept(this, RectIntersection);
    }

    public static ShapeVisitor<Rect, Shape> RectIntersection = new ShapeVisitor<Rect, Shape>() {
        public Shape visitRect(Rect otherShape, Rect thisShape) {
            // TODO...
        }
        public Shape visitLine(Line otherShape, Rect thisShape) {
            // TODO...
        }
        public Shape visitText(Text otherShape, Rect thisShape) {
            // TODO...
        }
    };
}
公共接口ShapeVisitor{
R visitRect(Rect Rect,P param);
R visitLine(Line-Line,P参数);
R visitText(文本,P参数);
}
公共界面形状{

R accept(P param,shapevisitorth)这是一个很好的技巧。有趣的是,
形状交集(Shape-Shape)
可以放在一个普通的抽象类中,但是这样做会完全破坏机制。这个“技巧”是因为你使用了静态吗?我不理解静态类中的“这个”。@peter.murray.rust这个“技巧”与我的相同-他使用已知的运行时类型并切换调用者和参数(
shape.intersection(this)
而不是
this.intersection(Line)shape)
).static
类是嵌套类的一种形式。普通类不能是
static
。只有在类中有一个类,才能将内部类声明为
static
。它在这里基本上没有效果,只是为了方便,以便他可以将其填充到单个类中。您可以安全地将其拆分为单独的类在单独的文件中。请参阅。我之所以说“trick”,是因为它使用方法重载,以便让编译器调用正确的
intersection
实现。但这很容易混淆,因此我不特别推荐这种技术;我认为调用特定方法
intersectionLine(Line-Line)更为清晰
相交圆(圆-圆)
等+1,因为这对我很有用。我遇到了rec.intersect(line)=line.intersect(rect),并用对另一个的调用替换了其中一个的主体。我们假设相交是对称的(不像collides():cyclist.collidesWith(car)和car.collidesWith(cyclist)需要不同的程序。@peter.murray.rust是的,我一直在考虑对称性-有很多不同的方法,但是如果你同意这一种,它很可能是最简单的一种。
public abstract class Shape {
    public final Shape intersect(Shape shape) {
        try {
            Method intersect = this.getClass().getMethod("intersect", shape.getClass());
            return (Shape)intersect.invoke(this, shape);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public abstract Shape intersect(Line line);
    public abstract Shape intersect(Rect rect);
}
public interface ShapeVisitor<P, R> { 
    R visitRect(Rect rect, P param);
    R visitLine(Line line, P param);
    R visitText(Text text, P param);
}

public interface Shape {
    <P, R> R accept(P param, ShapeVisitor<? super P, ? extends R> visitor);
    Shape intersectionWith(Shape shape);
}

public class Rect implements Shape {

    public <P, R> R accept(P param, ShapeVisitor<? super P, ? extends R> visitor) {
        return visitor.visitRect(this, param);
    }

    public Shape intersectionWith(Shape shape) {
        return shape.accept(this, RectIntersection);
    }

    public static ShapeVisitor<Rect, Shape> RectIntersection = new ShapeVisitor<Rect, Shape>() {
        public Shape visitRect(Rect otherShape, Rect thisShape) {
            // TODO...
        }
        public Shape visitLine(Line otherShape, Rect thisShape) {
            // TODO...
        }
        public Shape visitText(Text otherShape, Rect thisShape) {
            // TODO...
        }
    };
}