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

Seems like your Process logic sends out a lot of events. If you would give these events a name, you could subscribe your observers to them.

Then it would even be possible to create a 'filtering' observer that can forward the events to any other observer (a decorator pattern):

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);
      }
   }
};

You program could look like this:

Main main;

EventEventDispatcher f1;

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

main.listener=f1;

Note: code untested - grab the idea.

If you really want to decouple the sender from the sink, you put an event system in between. The example given here is very dedicated and lightweight, but do sure take a look at various existing implementations: Signals and Slots implemented in Qt and in Boost, the delegates from C#, ...

于 2012-06-29T19:07:59.063 回答
1

如果我错过了什么,我会道歉,但为什么不使用事件?基本上让IController暴露void 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动作所以不需要伤害/死亡方法,所以只需标记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 回答
0

我的最终解决方案是 xtofl 发布的 C# 等价物。我创建了一个类,其中存储了一堆代表。这些委托从默认值开始(因此它们永远不会为空),不同的具体 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();。它保证永远不会为空。

感谢 xtofl 的想法!

于 2012-07-02T17:34:24.007 回答