2

グラフィック プリミティブ (rect、line、circle など) の相互作用を分析し、オーバーラップ、相対方向、マージなどを計算しています。これは、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 は私の問題を解決し、(私にとっては) よりシンプルで読みやすいため、実際に @Slanec を実装しました。「解決策は関係が対称であるかどうかに依存しますか?」という副次的な質問があります。

「A が B と交差する」は通常、「B が A と交差する」と同じですが、「A が B と衝突する」と「B が A と衝突する」とは必ずしも同じではありません。(A == 車、B == サイクリスト)。私の交差点が今後対称的ではない可能性があると考えられます (たとえば、「Rect が部分的に円を隠す」は対称的ではなく、異なるセマンティクスを持つ可能性があります。

@Flavio はメンテナンスの問題にうまく対処しており、コンパイラが問題をチェックできることを指摘しています。@Slanec はこれをリフレクションを介して行います。これは、メンテナンスの補助として役立つように見えます。パフォーマンスへの影響はわかりません。

4

3 に答える 3

1

免責事項: 私はダブル ディスパッチについてあまり詳しくありません。見たことあるし、wikiの記事読んだことあるけどそれだけ。私はただ、できる限り最善を尽くして問題に取り組もうとしているだけです。


instanceof地獄_

Shape交差する両方のオブジェクトに関するクラス情報が実行時に認識されていることを利用できます。コードを実行すると、それが であり、パラメーターが typeでRectあることがわかりますが、メソッドが実行されると、具象型の正しくオーバーライドされたバージョンが呼び出されます。RectshapeShapeShape

以下のコードでは、正しいタイプで正しい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インターフェイスを anに変更して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);
}
于 2013-10-16T12:15:00.853 に答える
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

于 2013-10-16T12:17:44.393 に答える