3

私が頻繁に遭遇する基本的な問題ですが、明確な解決策を見つけたことはありますが、共通の基本クラスまたはインターフェイスの異なるオブジェクト間の相互作用の動作をコーディングする必要がある場合です。少し具体的にするために、例を挙げます。

ボブは、「クールな地理的効果」をサポートする戦略ゲームのコーディングを行っています。これらは、部隊が水中を歩いている場合、25% 減速するなどの単純な制約に切り上げられます。草の上を歩いている場合は 5% 遅くなり、歩道を歩いている場合は 0% 遅くなります。

現在、経営陣はボブに、新しい種類の軍隊が必要だと言いました。ジープ、ボート、ホバークラフトもあります。また、彼らはジープが水に浸かった場合にダメージを受けるようにし、ホバークラフトは 3 つの地形タイプすべてを無視するようにしました。噂によると、ユニットの速度を落としてダメージを受けるだけでなく、さらに多くの機能を備えた別の地形タイプを追加する可能性もあります.

非常に大まかな疑似コードの例を次に示します。

public interface ITerrain
{
    void AffectUnit(IUnit unit);
}

public class Water : ITerrain
{
    public void AffectUnit(IUnit unit)
    {
        if (unit is HoverCraft)
        {
            // Don't affect it anyhow
        }
        if (unit is FootSoldier)
        {
            unit.SpeedMultiplier = 0.75f;
        }
        if (unit is Jeep)
        {
            unit.SpeedMultiplier = 0.70f;
            unit.Health -= 5.0f;
        }
        if (unit is Boat)
        {
            // Don't affect it anyhow
        }
        /*
         * List grows larger each day...
         */
    }
}
public class Grass : ITerrain
{
    public void AffectUnit(IUnit unit)
    {
        if (unit is HoverCraft)
        {
            // Don't affect it anyhow
        }
        if (unit is FootSoldier)
        {
            unit.SpeedMultiplier = 0.95f;
        }
        if (unit is Jeep)
        {
            unit.SpeedMultiplier = 0.85f;
        }
        if (unit is Boat)
        {
            unit.SpeedMultiplier = 0.0f;
            unit.Health = 0.0f;
            Boat boat = unit as Boat;
            boat.DamagePropeller();
            // Perhaps throw in an explosion aswell?
        }
        /*
         * List grows larger each day...
         */
    }
}

おわかりのように、Bob が最初からしっかりとした設計文書を持っていれば、状況は改善されたでしょう。ユニット数と地形タイプが増えるにつれて、コードの複雑さも増します。ボブは、どのメンバーをユニット インターフェイスに追加する必要があるかを考える必要があるだけでなく、多くのコードを繰り返さなければなりません。新しい地形タイプでは、基本的な IUnit インターフェイスから取得できるものからの追加情報が必要になる可能性が非常に高くなります。

別のユニットをゲームに追加するたびに、新しいユニットを処理するために各地形を更新する必要があります。明らかに、これは処理されるユニットのタイプを決定する醜い実行時チェックは言うまでもなく、多くの繰り返しになります。この例では特定のサブタイプへの呼び出しをオプトアウトしましたが、そのような種類の呼び出しを行う必要があります。例としては、ボートが陸地に衝突したときにプロペラが損傷することがあります。すべてのユニットにプロペラがあるわけではありません。

この種の問題が何と呼ばれているのかはわかりませんが、それは多対多の依存関係であり、切り離すのに苦労しています。カップリングをきれいにしたいので、ITerrain の各 IUnit サブクラスに何百ものオーバーロードがあるのは好きではありません。

この問題に関する何らかの光明が非常に求められています。おそらく、私は軌道から外れていると考えていますか?

4

5 に答える 5

1

相互作用ルールをUnitクラスとTerrainクラスから切り離します。相互作用のルールはそれよりも一般的です。たとえば、ハッシュテーブルは、キーが相互作用するタイプのペアであり、値がそれらのタイプのオブジェクトを操作する「エフェクター」メソッドである場合に使用できます。

2つのオブジェクトが相互作用する必要がある場合は、ハッシュテーブルですべての相互作用ルールを見つけて実行します

これにより、元の例の恐ろしいswitchステートメントは言うまでもなく、クラス間の依存関係がなくなります。

パフォーマンスが問題になり、実行中に相互作用ルールが変更されない場合は、タイプペアのルールセットが検出されたときにキャッシュし、新しいMSILメソッドを発行してそれらをすべて一度に実行します。

于 2008-09-16T03:18:15.503 に答える
1

Terrain has-a Terrain Attribute

地形属性は多次元です。

ユニットには推進力があります。

推進力は地形属性と互換性があります。

ユニットは、推進力を引数として地形を訪問することで移動します。それは推進力に委譲されます。

ユニットは、訪問の一環として地形の影響を受ける場合があります。

ユニットコードは推進力について何も知りません。地形タイプは、地形属性と推進力以外は何も変更せずに変更できます。Propuslion のコンストラクターは、既存のユニットを新しい移動手段から保護します。

于 2008-09-16T01:49:03.223 に答える
1

ここで直面している制限は、他の OOP 言語とは異なり、C# には複数の dispatchがないことです。

つまり、これらの基本クラスが与えられた場合:

public class Base
{
    public virtual void Go() { Console.WriteLine("in Base"); }
}

public class Derived : Base
{
    public virtual void Go() { Console.WriteLine("in Derived"); }
}

この機能:

public void Test()
{
    Base obj = new Derived();
    obj.Go();
}

参照「obj」が Base 型であっても、「in Derived」を正しく出力します。これは、実行時にC# が呼び出す最も派生した Go() を正しく見つけるためです。

ただし、C# は単一のディスパッチ言語であるため、OOP 言語では暗黙的に "this" である "最初のパラメーター" に対してのみこれを行います。次のコードは上記のようには機能しません。

public class TestClass
{
    public void Go(Base b)
    {
        Console.WriteLine("Base arg");
    }

    public void Go(Derived d)
    {
        Console.WriteLine("Derived arg");
    }

    public void Test()
    {
        Base obj = new Derived();
        Go(obj);
    }
}

これは、「this」を除いて他のすべてのパラメーターが静的にディスパッチされるため、「Base arg」を出力します。つまり、コンパイル時に呼び出されたメソッドにバインドされます。コンパイル時にコンパイラが知っているのは、渡される引数の宣言された型 ("Base obj") だけであり、実際の型ではないため、メソッド呼び出しは Go(Base b) にバインドされます。

あなたの問題の解決策は、基本的に小さなメソッドディスパッチャを手作業で作成することです:

public class Dispatcher
{
    public void Dispatch(IUnit unit, ITerrain terrain)
    {
        Type unitType = unit.GetType();
        Type terrainType = terrain.GetType();

        // go through the list and find the action that corresponds to the
        // most-derived IUnit and ITerrain types that are in the ancestor
        // chain for unitType and terrainType.
        Action<IUnit, ITerrain> action = /* left as exercise for reader ;) */

        action(unit, terrain);
    }

    // add functions to this
    public List<Action<IUnit, ITerrain>> Actions = new List<Action<IUnit, ITerrain>>();
}

リフレクションを使用して、渡された各アクションの一般的なパラメーターを検査し、指定されたユニットと地形に一致する最も派生したものを選択して、その関数を呼び出すことができます。アクションに追加された関数は、複数のアセンブリに分散されていても、どこにでも配置できます。

興味深いことに、私はこの問題に数回遭遇しましたが、ゲームのコンテキスト以外では決して遭遇しませんでした.

于 2008-09-16T02:27:51.227 に答える
1

ここでは、間違いなく 3 つのオブジェクトが使用されています。

1) 地形
2) 地形効果
3) ユニット

アクションを検索するためのキーとして、地形/ユニットのペアを使用してマップを作成することはお勧めしません。これにより、ユニットと地形のリストが増えるにつれて、すべての組み合わせを確実に網羅することが難しくなります。

実際、すべての地形とユニットの組み合わせには固有の地形効果があるように見えるため、共通の地形効果リストを使用するメリットがあるとは考えにくい.

代わりに、各ユニットに独自の地形対地形マップを維持させます。次に、地形は Unit->AffectUnit(myTerrainType) を呼び出すだけで、ユニットは地形がそれ自体に与える影響を調べることができます。

于 2008-09-16T03:51:36.877 に答える
0

古い考え:

クラス iTerrain と、各ユニット タイプに影響を与えるメソッドを含む地形タイプである引数を受け入れる別のクラス iUnit を作成します。

例:

  boat = new
iUnit("watercraft") field = new
iTerrain("grass")
field.effects(boat)

わかりました、私がより良い考えを持っていることをすべて忘れてください:

各地形の効果を各ユニットの属性にする

例:


public class hovercraft : unit {
    #You make a base class for defaults and redefine as necessary
    speed_multiplier.water = 1
}

public class boat : unit {
    speed_multiplier.land = 0
}
于 2008-09-16T01:31:29.257 に答える