6

オブジェクト指向の設計で問題が発生し、2 つの異なるクラスでコードが重複してしまいます。何が起こっているかは次のとおりです。

この例では、ゲーム オブジェクト間の衝突を検出したいと考えています。

共通メソッド (checkForCollisionWith など) を保持する基本 CollisionObject と、基本クラスを拡張する CollisionObjectBox、CollisionObjectCircle、CollisionObjectPolygon があります。

設計のこの部分は問題ないように見えますが、私を悩ませているのは次のとおりです。

aCircle checkForCollisionWith: aBox

Circle サブクラス内で円対ボックスの衝突チェックを実行します。逆に、

aBox checkForCollisionWith: aCircle

Box サブクラス内でボックスと円の衝突チェックを実行します。

ここでの問題は、Box クラスと Circle クラスの両方にあるため、Circle と Box の衝突コードが重複していることです。これを回避する方法はありますか、またはこの問題に間違った方法で取り組んでいますか? 今のところ、重複コードをすべて含むヘルパー クラスを用意し、重複を避けるために aCircle および aBox オブジェクトから呼び出します。ただし、これに対するよりエレガントな OO ソリューションがあるかどうかは興味があります。

4

7 に答える 7

3

あなたが望むものはマルチディスパッチと呼ばれます。

複数のディスパッチまたはマルチメソッドは、一部のオブジェクト指向プログラミング言語の機能であり、複数の引数の実行時 (動的) 型に基づいて、関数またはメソッドを動的にディスパッチできます。

これはメインラインの OOP 言語でエミュレートできます。また、Common Lisp を使用している場合は直接使用できます。

ウィキペディアの記事の Java の例では、正確な問題である衝突検出も扱っています。

これが私たちの「現代」言語での偽物です。

abstract class CollisionObject {
    public abstract Collision CheckForCollisionWith(CollisionObject other);
}

class Box : CollisionObject {
    public override Collision CheckForCollisionWith(CollisionObject other) {
        if (other is Sphere) { 
            return Collision.BetweenBoxSphere(this, (Sphere)other);
        }
    }
}

class Sphere : CollisionObject {
    public override Collision CheckForCollisionWith(CollisionObject other) {
        if (other is Box) { 
            return Collision.BetweenBoxSphere((Box)other, this);
        }
    }
}

class Collision {
    public static Collision BetweenBoxSphere(Box b, Sphere s) { ... }
}

Common Lisp では次のようになります。

(defmethod check-for-collision-with ((x box) (y sphere))
   (box-sphere-collision x y))

(defmethod check-for-collision-with ((x sphere) (y box))
   (box-sphere-collision y x))

(defun box-sphere-collision (box sphere)
    ...)
于 2009-12-06T18:23:56.530 に答える
3

これは、OO 開発における典型的な落とし穴です。私もかつてこの方法で衝突を解決しようとしましたが、惨めに失敗しました。

これは所有権の問題です。Box クラスは本当に円との衝突ロジックを所有していますか? なぜ逆にしないのですか?結果は、コードの重複またはサークルからボックスへの衝突コードの委譲です。どちらもきれいではありません。二重ディスパッチはこれを解決しません-所有権に関する同じ問題...

その通りです。特定の衝突を解決するサード パーティの関数/メソッドと、衝突している 2 つのオブジェクトに対して適切な関数を選択するメカニズムが必要です (ここでは、ダブル ディスパッチを使用できますが、衝突プリミティブの数が制限されている場合は、おそらく 2D 配列になります)。のファンクターは、少ないコードでより高速なソリューションです)。

于 2009-12-06T18:51:59.343 に答える
1

I had the same problem (working in Objective C), and a workaround I found for this is to define an external function to solve the collision when I already know types for both objects.

For example, if I have Rectangle and Circle, both implementing a protocol (kind of interface for this language) Shape..

@protocol Shape

-(BOOL) intersects:(id<Shape>) anotherShape;
-(BOOL) intersectsWithCircle:(Circle*) aCircle;
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle;

@end

define intersectsWithCircle for Rectangle, and intersectsWithRectangle for Circle like this

-(BOOL) intersectsWithCircle:(Circle*) aCircle
{
    return CircleAndRectangleCollision(aCircle, self);
}

and ...

-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle
{
    return CircleAndRectangleCollision(self, aRectangle);
}

Of course it doesn't attack the coupling problem of Double Dispatch, but at least it avoids code duplication

于 2011-01-21T17:05:28.690 に答える
1

checkForCollisionWith: aCollisionObject を使用する必要があります。すべてのオブジェクトが CollisionObject を拡張しているため、すべての共通ロジックをそこに置くことができます。

または、委任設計パターンを使用して、異なるクラス間で共通のロジックを共有することもできます。

于 2009-12-06T18:27:40.453 に答える
1

あなたが使用している言語を言っていないので、Java や C# のようなものだと思います。

これは、マルチメソッドが理想的なソリューションである状況ですが、ほとんどの言語はそれらをサポートしていません。それらをエミュレートする通常の方法は、訪問者パターンのバリエーションを使用することです。設計パターンに関する優れた本を参照してください。

あるいは、オブジェクトのペア間の衝突をチェックする別の CollisionDetection クラスを用意し、2 つのオブジェクトが衝突した場合は、bomb.explode() や player.die() など、オブジェクトの適切なメソッドを呼び出します。このクラスは、行と列に沿って各オブジェクト タイプを含む大きなルックアップ テーブルと、両方のオブジェクトで呼び出すメソッドを提供するエントリを持つことができます。

于 2009-12-06T18:28:26.193 に答える
0

最初のオプション:衝突を指向性にします。たとえば、ボックスが静止している場合、他のものとの衝突をチェックしません。ただし、移動する円は、ボックス(およびその他の静止オブジェクト)との衝突をチェックします。私たちの人生はすべて「平等で反対の反応」を教えられているので、これは直感的ではありません。落とし穴:移動するオブジェクトは、他の移動するオブジェクトとの衝突を複製します。


2番目のオプション:すべてのオブジェクトに一意のID番号を付けます。衝突チェック方法では、最初のパラメータ/オブジェクトのIDが2番目のパラメータよりも小さい場合にのみ衝突をチェックします。

ボックスにid=2があり、円にid=5があるとします。次に、box.id <circle.id;であるため、「ボックスが円と衝突する」が実行されます。ただし、円が衝突をチェックしている場合、衝突はすでにチェックされているため、「円はボックスと衝突します」は衝突をチェックせずにすぐに戻ります。

于 2009-12-07T06:21:20.047 に答える
0

おそらく、さまざまなタイプの衝突をテストするためのメソッドを含む衝突オブジェクトを持つことができます。メソッドは、衝突ポイントとその他の必要な情報を含む他のオブジェクトを返すことができます。

于 2009-12-06T18:22:33.460 に答える