2

次のクラス階層を想像してみてください。

interface IRules
{
    void NotifyPickup(object pickedUp);
    void NotifyDeath();
    void NotifyDamage();
}

class CaptureTheFlag : IRules
{
    public void NotifyPickup(Pickup pickedUp)
    {
        if(pickedUp is Flag)
            GameOver();
    }

    public void NotifyDeath()
    {
    }

    public void NotifyDamage()
    {
    }
}

class DeathMatch : IRules
{
    public void NotifyPickup(Pickup pickedUp)
    {
        points++;
    }

    public void NotifyDeath()
    {
        lives--;
    }

    public void NotifyDamage()
    {
    }
}

class GameWorld
{
    IRules gameMode;

    public Main(IRules gameMode)
    {
        this.gameMode = gameMode;
    }

    object[] worldObjects;

    public void GameLoop()
    {
        foreach(object obj in worldObjects)
        {
            // This call may have a bunch of sideeffects, like getting a pickup
            // Or a player dying
            // Or damage being taken
            // Different game modes are interested in different events / statistics.
            obj.Update();


            // Stuff happens...
            gameMode.NotifyDamage();
            // Stuff happens...
            gameMode.NotifyDeath();
        }
    }
}

だからここに私はNotify*関数を含むインターフェースを持っています。これらはコールバックです。さまざまなゲームモードが、ゲームのさまざまなイベントに関心を持っています。これらのイベントはworldObjects配列に埋め込まれているため、これらのイベントを作成する具象オブジェクトにアクセスすることは実際には不可能です。ゲームに新しいゲームモードを追加していると想像してください。IRulesインターフェースは非常に肥大化し、ゲームモードが関心を持つ可能性のあるすべてのものが含まれ、ほとんどの呼び出しはスタブされます!どうすればこれを防ぐことができますか?

編集2:具体的な例

4

3 に答える 3

1

何かを逃した場合はお詫びしますが、イベントを使用してみませんか?基本的にIControllerexposevoid Callback()メソッドを使用すると、Mainは任意のコールバックを自分のイベントにサブスクライブできます。

class Main
{
    private event EventHandler SomeEvent;

    public Main(IController controller)
    {
        // use weak events to avoid memory leaks or
        // implement IDisposable here and unsubscribe explicitly
        this.SomeEvent += controller.Callback;
    }

    public void ProcessStuff()
    {
        // invoke event here
        SomeEvent();
    }        
}

編集:

これが私が行うことです。各ルールアクションを個別のインターフェイスに抽出して、具体的なクラスに必要なものを実装するだけです。たとえば、CaptureTheFlagクラスは今のところアクションのみPickupFlagを実行するため、Damage / Deathメソッドは必要ないので、マークを付けるだけIPickupableです。 。次に、具象インスタンスが具象アクションをサポートしているかどうかを確認し、実行を続行します。

interface IPickupable
{
    void NotifyPickup(object pickedUp);
}

interface IDeathable
{
    void NotifyDeath();
}

interface IDamagable
{
    void NotifyDamage();
}    

class CaptureTheFlag : IPickupable
{
    public void NotifyPickup(Pickup pickedUp)
    {
        if (pickedUp is Flag)
            GameOver();
    }
}

class DeathMatch : IPickupable, IDeathable
{
    public void NotifyPickup(Pickup pickedUp)
    {
        points++;
    }

    public void NotifyDeath()
    {
        lives--;
    }
}

class GameWorld
{
    public void GameLoop()
    {       
        foreach(object obj in worldObjects)     
        {
            obj.Update();
            IPickupable pickupable = gameMode as IPickupable;
            IDeathable deathable = gameMode as IDeathable; 
            IDamagable damagable = gameMode as IDamagable;
            if (pickupable != null)
            {
                pickupable.NotifyPickup();
            }

            if (deathable != null)
            {
                deathable.NotifyDeath();
            }

            if (damagable != null)
            {
                damagable.NotifyDamage();
            }                      
         }
     }
}
于 2012-06-29T18:50:01.927 に答える
1

Processロジックが多くのイベントを送信しているようです。これらのイベントに名前を付ける場合は、オブザーバーをサブスクライブできます。

次に、イベントを他のオブザーバー(デコレーターパターン)に転送できる「フィルタリング」オブザーバーを作成することも可能です。

struct Event {
  enum e { A, B, /*...*/ };
  e name;
};

class IEventListener {
public:
   virtual void event( Event e ) = 0;
};

// an event dispatcher implementation:
using namespace std;

class EventDispatcher {
public:
   typedef std::shared_ptr<IEventListener> IEventListenerPtr;
   map<Event::e,vector<IEventListenerPtr>> listeners;

   void event(Event e){ 
      const vector<IEventListenerPtr> e_listeners=listeners[e.name].second;
      //foreach(begin(e_listeners)
      //       ,end(e_listeners)
      //       ,bind(IEventListener::event,_1,e));
      for(vector<IEventListenerPtr>::const_iterator it=e_listeners.begin()
         ; it!=e_listeners.end()
         ; ++it)
      {
        (*it)->event(e);
      }
   }
};

プログラムは次のようになります。

Main main;

EventEventDispatcher f1;

f1.listeners[Event::A].push_back(listener1);

main.listener=f1;

注:コードはテストされていません-アイデアをつかんでください。

送信者をシンクから本当に切り離したい場合は、その間にイベントシステムを配置します。ここに示す例は非常に専用で軽量ですが、さまざまな既存の実装を確認してください。QtBoostで実装されたシグナルとスロット、C#の代表者、...

于 2012-06-29T19:07:59.063 に答える
0

私の最終的な解決策は、xtoflが投稿したものと同等のC#でした。多数のデリゲートを格納するクラスを作成しました。これらのデリゲートはデフォルト値で開始し(したがって、nullになることはありません)、さまざまな具象IRulesクラスがそれらを上書きするかどうかを選択できます。これは、無関係のメソッドでインターフェイスを詰まらせないため、抽象メソッドやスタブメソッドよりもうまく機能しました。

class GameEvents
{
    public Action<Player> PlayerKilled = p => {};
    public Func<Entity, bool> EntityValid = e => true;
    public Action ItemPickedUp = () => {};
    public Action FlagPickedUp = () => {};
}

class IRules
{
    GameEvents Events { get; }
}

class CaptureTheFlag : IRules
{
    GameEvents events = new GameEvents();
    public GameEvents Events
    {
        get { return events; }
    }

    public CaptureTheFlag()
    {
        events.FlagPickedUp = FlagPickedUp;
    }

    public void FlagPickedUp()
    {
        score++;
    }
}

各ルールセットは、リッスンするイベントを選択できます。ゲームは、Rules.Events.ItemPickedUp();を実行することによって単純に呼び出します。nullになることはありません。

アイデアをくれたxtoflに感謝します!

于 2012-07-02T17:34:24.007 に答える