4

叔父の勧めでJavaからC#に飛び込んでいます。JavaジオメトリライブラリはC#の描画ライブラリよりも完全に見えるので、C#を開始するために、機能を少し追加した単純な移植に取り組んでいます。

しかし、私は設計上の問題に遭遇し、どちらがより良い選択であるかを見分けることができません。具体的なデータ型をとる抽象基本クラスに複数のメソッドを含めるか、それとも引数として抽象ベースをとるメソッドを少なくするか。

public abstract bool isOverlapping(GeometricObject2D o) {}

また

public abstract bool isOverlapping(Rectangle2D rect) {}
public abstract bool isOverlapping(Circle2D circ) {}
etc...

私が頭の中で持っている議論は、具体的な議論が論理エラーを防ぐことを教えてくれますが、使用が適切である場合は常に抽象データ型を使用するように教えられてきました。

4

4 に答える 4

8

操作を基本クラスに配置する場合は、抽象型を使用します。新しいサブクラスを追加するたびに基本クラスを変更する必要はありません。

別の方法は、ビジターパターンのようなものを使用し、各具体的なクラスを順番にビジターにディスパッチさせることです。交差点の訪問者には、オブジェクトタイプの各ペアの交差点を計算する方法に関するすべての知識が含まれます。

これにビジターパターンを使用する方法は次のとおりです。(私はC#プログラマーではないので、Java構文を使用します)。まず、ここでのビジターパターンの使用は、 2つの引数のタイプに基づいて操作を変更する必要があるため、通常の場合よりも複雑です。実際には、トリプルディスパッチが必要です。Clojureのような言語はこれを直接サポートしますが、Java(およびおそらくC#)では、2つのレベルのビジターを使用してトリプルディスパッチをシミュレートする必要があります。醜いですが、大きな利点は、ジオメトリ階層をクリーンで保守しやすくし、すべての交差ロジックを1つのコンパイルユニットに集中化できることです。

public interface IGeometry {
    void accept(IGeometryVisitor visitor);
}

public interface IGeometryVisitor {
    void visitCircle2D(Circle2D circle);
    void visitBox2D(Box2D box);
    // a method for each concrete type
}

public class Circle2D implements IGeometry {
    public void accept(IGeometryVisitor visitor) {
        visitor.visitCircle2D(this);
    }
}

public class Box2D implements IGeometry {
    public void accept(IGeometryVisitor visitor) {
        visitor.visitBox2D(this);
    }
}

public class IntersectionVisitor implements IGeometryVisitor {
    private boolean mResult;
    private IGeometry mGeometry2;

    public static boolean isOverlapping(IGeometry geometry1, IGeometry geometry2) {
        return new IntersectionVisitor(geometry1, geometry2).mResult;
    }

    private IntersectionVisitor(IGeometry geometry1, IGeometry geometry2) {
        mGeometry2 = geometry2;
        // now start the process
        mGeometry1.accept(this);
    }

    public void visitCircle2D(Circle2D circle) {
        mGeometry2.accept(new Circle2DIntersector(circle));
    }

    private class Circle2DIntersector implements IGeometryVisitor {
        private Circle2D mCircle;
        Circle2DIntersector(Circle2D circle) {
            mCircle = circle;
        }
        public void visitCircle2D(Circle2D circle) {
            mResult = isOverlapping(mCircle, circle);
        }
        public void visitBox2D(Box2D box) {
            mResult = isOverlapping(mCircle, box);
        }
    }

    private class Box2DIntersector implements IGeometryVisitor {
        private Box2D mBox;
        Box2DIntersector(Box2D box) {
            mBox = box;
        }
        public void visitCircle2D(Circle2D circle) {
            mResult = isOverlapping(circle, mBox);
        }
        public void visitBox2D(Box2D box) {
            mResult = isOverlapping(mBox, box);
        }
    }

    // static methods to compute overlap of concrete types
    // For N concrete types there will be N*(N+1)/2 methods
    public static boolean isOverlapping(Circle2D circle1, Circle2D circle2) {
        return /* intersection of 2 circles */;
    }

    public static boolean isOverlapping(Circle2D circle, Box2D box) {
        return . . .;
    }

    public static boolean isOverlapping(Box2D box1, Box2D box2) {
        return . . .;
    }
}
于 2012-11-08T15:27:49.793 に答える
5

ダブルディスパッチランドへようこそ!あなたが見ている問題は、仮想ディスパッチを使用した言語の欠点の典型的な例です。理想的には、2つの形状が重なるかどうかを判断するアルゴリズムは両方の形状に依存するため、複数のオブジェクトに関して仮想的な関数を探しています。

2番目のコードスニペット(複数の具象クラスを含む)は、ビジターパターンと呼ばれるダブルディスパッチ問題の1つの一般的な解決策への出発点です。--- sifのチェーンよりもうまく機能しますが、いくつかの欠点があります。thenelse

  • 新しい形状を追加するたびに、新しく追加された形状とのオーバーラップをチェックする方法ですべての形状を拡張する必要があります
  • たとえば、 「s」または「s」でRectangle2Dオーバーラップする決定的なアルゴリズムをどこで探すかは明確ではありません。Circle2DRectangle2DIsOverlapping(Circle2D)Circle2DIsOverlapping(Rectangle2D)

一般的な解決策の1つは、タイプIDを導入し、幾何学的形状のオーバーラップを処理するデリゲートの2D配列を作成することです。これは訪問者の最初の問題に悩まされますが、意思決定を一元化することによって2番目の問題を修正します。

于 2012-11-08T15:38:24.203 に答える
2

私がすること:

public interface IGeometry
{
    bool IsOverlapping(IGeometry geometry);
}

public class Circle2D : IGeometry
{
    public bool IsOverlapping(IGeometry geometry)
    {
        dynamic dyn = geometry;
        return Overlapper.Overlap(this, dyn);
    }
}

public class Box2D : IGeometry
{
    public bool IsOverlapping(IGeometry geometry)
    {
        dynamic dyn = geometry;
        return Overlapper.Overlap(this, dyn);
    }
}

public static class Overlapper
{
    public static bool Overlap(Box2D box1, Box2D box2)
    {
        // logic goes here
    }

    public static bool Overlap(Box2D box1, Circle2D circle1)
    {
        // logic goes here
    }

    public static bool Overlap(Circle2D circle1, Box2D box1)
    {
        return Overlap(box1, circle1); // No need to rewrite it twice
    }

    public static bool Overlap(Circle2D circle1, Circle2D circle2)
    { 
        // logic goes here
    }
}

神よ、私の答えは愚かです。この場合、とにかく他のオブジェクトを呼び出す必要はなく、ペアを静的クラスに直接送信するだけで済みます。とにかく...私の推測では、それを行うための印象的に簡単な方法はありません。

于 2012-11-08T15:52:56.833 に答える
1

2つの形状が重なっているかどうかを判断するための汎用ロジックを実装できるとは思わないのでisOverlapping、すべてのタイプでオーバーロードすることをお勧めします。

抽象型を引数として使用する場合でも、問題の具象型をチェックして、関連する計算を実行する必要があります。ここでの問題は、解決策があまり明確ではないことです。GeometricObject2Dで実装されていない具象型を渡すことができますisOverlapping。じゃあ何?isOverlapping(GeometricObject2D o)定義上、呼び出しは技術的に歓迎されるため、ここで例外をスローすることは適切ではありません。「ほぼすべてのGeometricObject2D型を受け入れます!」と言うのはOOPのポイントを打ち負かします。

于 2012-11-08T15:32:32.303 に答える