界面膨胀与回调

时间:2012-06-29 18:46:43

标签: oop design-patterns interface dependency-injection

想象一下以下的类层次结构:

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:具体示例

3 个答案:

答案 0 :(得分: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();
            }                      
         }
     }
}

答案 1 :(得分: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#的代理,......

答案 2 :(得分: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();来调用。保证永远不会为空。

感谢xtofl的想法!