2

我正在分析图形基元(矩形、直线、圆形等)的交互,并计算重叠、相对方向、合并等。这被引用为 Double Dispatch 的主要示例(例如Wikipedia

自适应碰撞算法通常要求以不同方式处理不同对象之间的碰撞。一个典型的例子是在游戏环境中,宇宙飞船和小行星之间的碰撞与宇宙飞船和空间站之间的碰撞的计算方式不同。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 == 骑自行车的人)。可以想象,我的交叉点在未来可能不是对称的(例如“矩形部分遮住圆”不是对称的并且可能具有不同的语义。

@Flavio 很好地解决了维护问题,并指出编译器可以检查问题。@Slanec 通过反射来做到这一点,这看起来像是一种有用的维护辅助工具——我不知道性能损失是什么。

4

3 回答 3

1

You can implement double dispatch in Java through the Visitor pattern.

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...
        }
    };
}

When you add a new Shape subclass, you must add a new method to the ShapeVisitor interface, and you get compile errors for all the methods you are missing. This is useful, but can become a big problem if you are writing a library and your users are allowed to add Shape subclasses (but clearly can not extend the ShapeVisitor interface).

于 2013-10-16T12:17:44.393 回答
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
于 2013-10-16T12:13:45.777 回答
1

免责声明:我对双重调度不是很熟悉。我已经看过了,我已经阅读了 wiki 文章,但仅此而已。我只是想尽我所能解决这个问题。


instanceof地狱_

我们可以利用关于两个相交Shape对象的类信息在运行时是已知的。运行您的Rect代码知道它是 aRect并且shape参数是 type 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接口更改为 anabstract 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);
}
于 2013-10-16T12:15:00.853 回答