Java 如何使用双重分派来分析图形原语的交集?
我正在分析图形原语(rect、line、circle等)的交互作用,并计算重叠、相对方向、合并等。这是双重分派(例如 自适应碰撞算法通常要求 不同的对象可以用不同的方式处理。一个典型的例子是 在一个游戏环境中,宇宙飞船和宇宙飞船之间的碰撞 小行星的计算不同于 宇宙飞船和空间站 但是我还不明白主要的解释,我也不太明白答案 我当前的代码(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 =
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...
}
};
}