我正在分析图形基元(矩形,直线,圆形等)的相互作用,并计算重叠,相对方向,合并等。这被引用作为双重调度的主要示例(例如Wikipedia )
自适应碰撞算法通常需要在它们之间发生碰撞 不同的对象以不同的方式处理。一个典型的例子是 在游戏环境中,宇宙飞船和飞船之间的碰撞 小行星的计算方式与a之间的碰撞不同 太空飞船和太空站。1
但是我还没有理解主要的解释,我也一般不理解SO的答案。
我当前的代码(Java)使用超类Shape,如下所示:
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));
}
}
在子类(此处为Rect)中具体实现,例如
public class Rect extends Shape {
public Shape intersectionWith(Shape shape) {
if (shape instanceof Rect) {
return this.getCommonBoundingBox((Rect)shape);
} else if (shape instanceof Line) {
return this.intersection((Line)shape);
} else if (shape instanceof Text) {
return this.intersection((Text) shape);
}
}
}
无论如何我必须编写所有n*(n-1)/2
方法(并且已经这样做了)。我还必须有可扩展的代码以便在以后适应(比如说):
} else if (shape instanceof Circle) {
return this.intersection((Circle)shape);
我没有看到双重调度模式的使用方法或价值,并且会欣赏使用Java图形基元或类似伪代码的具体示例。
更新:我接受了@Flavio(我认为)它回答了提出的确切问题。但是我实际上已经实现了@Slanec,因为它解决了我的问题,而且(对我而言)更简单,更容易阅读。我有一个附属问题“解决方案取决于对称关系吗?”。
“A交叉B”通常与“B交叉A”相同,但“A与B碰撞”并不总是与“B与A碰撞”相同。 (A ==汽车,B ==骑车人)。可以想象我的交叉点在未来可能不对称(例如,“Rect partial obscures Circle”不对称且可能具有不同的语义。
@Flavio很好地解决了维护问题,并指出编译器可以检查问题。 @Slanec通过反射来做到这一点,看起来好像它是一个有用的维护辅助工具 - 我不知道它的性能是什么。
答案 0 :(得分:1)
我认为会是这样的:
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;
}
}
}
示例的输出是:
circle + line
rect + line
rect + circle
答案 1 :(得分:1)
免责声明:我对Double dispatch并不熟悉。我已经看过了,我已经阅读了维基文章,但就是这样。我只是想尽力解决这个问题。
instanceof
地狱我们可以利用关于两个相交的Shape
对象的类信息在运行时是已知的。运行代码的Rect
知道它是Rect
而shape
参数的类型为Shape
,但是当运行方法时,它会调用正确覆盖的版本具体的Shape
类型。
在下面的代码中,将在正确的intersect()
类型上调用正确的Shape
重载:
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
接口更改为abstract class
并在那里使用该方法,那么将不会工作,因为该方法将递归调用自身:
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);
}
答案 2 :(得分:1)
您可以通过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...
}
};
}
当您添加新的Shape
子类时,必须向ShapeVisitor
接口添加新方法,并且您将丢失所有缺少的方法的编译错误。这很有用,但如果您正在编写库并且允许您的用户添加Shape
子类(但显然无法扩展ShapeVisitor
接口),则可能会成为一个大问题。