【问题标题】:Interface inheritance/hierarchies in .Net - is there a better way?.Net 中的接口继承/层次结构 - 有没有更好的方法?
【发布时间】:2012-09-11 21:53:21
【问题描述】:

我想我会在我的代码中使用一个案例研究作为我的意思的例子。

现在,我正在为我的基于组件的游戏系统开发一个行为/动作系统。 GameObjects 可以有一个 IBehaviorComponent 和一个 IActionComponent 附加到它们,它们只显示相关的细节,暴露以下内容:

public interface IBehaviorComponent : IBaseComponent
{
   IBehavior Behavior { get; }
}

public interface IActionComponent : IBaseComponent
{
   IAction Action { get; }
   void ExecuteAction(IGameObject target = null);
}

现在,到目前为止一切都很好(至少对我来说!)。但是,当我查看 IActionComponent 的实现时,麻烦就开始出现了。

例如一个简单的 IActionComponent 实现:

public class SimpleActionComponent : IActionComponent
{
   public IAction Action { get; protected set; }

   public void ExecuteAction(IGameObject target = null)
   {
      Action.Execute(target);
   }

   public void Update(float deltaTime) { } //from IBaseComponent
}

但是,假设我想引入一个更复杂的 IActionComponent 实现,它允许按时间安排执行操作:

public class TimedActionComponent : IActionComponent
{
   public IAction Action { get; protected set; }

   public float IdleTime { get; set; }

   public float IdleTimeRemaining { get; protected set; }

   public void ExecuteAction(IGameObject target = null)
   {
      IdleTimeRemaining = IdleTime;
   }

   public void Update(float deltaTime)
   {
      if (IdleTimeRemaining > 0f)
      {
         IdleTimeRemaining -= deltaTime;
      }
      else
      {
         Action.Execute(target);
      }
   }
}

现在,假设我想公开 IdleTime 以便可以通过外部影响对其进行更改。一开始我的想法是创建一个新界面:

public interface ITimedActionComponent : IActionComponent
{
   float IdleTime { get; set; }

   float IdleTimeRemaining { get; set; }
}

但是,这里的问题是我的组件系统将所有内容都存储在 IBaseComponent 的上一级。因此,GameObject 的动作组件被检索为 IActionComponent,而不是 ITimedActionComponent,或 IRandomizedActionComponent,甚至 ICrashTheProgramActionComponent。我希望这样做的原因是显而易见的,因为我希望任何东西都能够查询 GameObject 的其中一个组件,而不必确切知道它除了基本类型的组件(IActionComponent、IRenderableComponent、IPhysicsComponent 等)之外想要什么。 /p>

是否有一种更简洁的方法来处理这个问题,这将允许我公开在子类中定义的这些属性,而不必将检索到的 IActionComponent 转换为它感兴趣的类型?或者这仅仅是实现这一目标的唯一/最佳方式。比如:

public void IWantYouToAttackSuperSlow(IGameObject target)
{
   //Let's get the action component for the gameobject and test if it's an ITimedActionComponent...
   ITimedActionComponent actionComponent = target.GetComponent<IActionComponent>() as ITimedActionComponent;

   if (actionComponent != null) //We're okay!
   {
      actionComponent.IdleTime = int.MaxValue; //Mwhaha
   }
}

现在我在想那是唯一的方法,但我想我会看看木制品中是否隐藏着我不知道的图案,或者是否有人可以提出更好的建议实现这一点的方法开始。

谢谢!

【问题讨论】:

  • 我的另一个想法是在内部使用另一个对象,例如 IActionComponentExecutor,它会决定何时调用 Action.Execute(),但我认为这让我回到了一个圆-a-回合时尚 - 然后外部对象将不得不追踪 IActionComponentTimedExecutor(哦~我们现在开始'企业-y!)以更改 IdleTime。
  • 出于兴趣 - actionComponent.IdleTime 的状态是否存储在某种通用包中?如果是这样,您可以在基本接口上使用 Apply(BagData bag)Store(BagData bag) 方法。也就是说,您可能还需要考虑一些元数据类型系统,您可以使用它来查询需要知道派生类型的属性。 TypeDescriptor 是一个现成的起点,供属性网格等使用。
  • 不,目前不是。我也不完全熟悉这个概念 - 您是否可以提供参考?
  • 好吧,真的,我想它就像序列化一样。但是,如果您希望能够编写这样的代码,因为它知道派生接口,所以它可能不适合“知道”存在可以设置的值。
  • 好的,有道理。不过,我认为对于这种情况,它可能有点一般。其他代码将知道派生接口,我只需要一个很好的方法来获得该访问权限。

标签: c# inheritance interface


【解决方案1】:

您展示的将IActionComponent 转换为ITimedActionComponent 的代码(据我所知)是不可避免的——您必须了解IdleTime 属性才能使用它们,对吧?

我认为这里的诀窍是隐藏看起来不太好看的代码,使用您的 IActionComponent 的类不必处理它。
我对如何做到这一点的第一个想法是使用Factory

  public void IWantYouToAttackSuperSlow(IGameObject target)
{
   //Let's get the action component for the gameobject 
   IActionComponent actionComponent = ActionComponentsFactory.GetTimedActionComponentIfAvailable(int.MaxValue); 
}

和你工厂的方法:

public IActionComponent GetTimedActionComponentIfAvailable(IGameObject target, int idleTime)
{
var actionComponent = target.GetComponent<IActionComponent>() as ITimedActionComponent;

   if (actionComponent != null) //We're okay!
   {
      actionComponent.IdleTime = int.MaxValue; //Mwhaha
   }
return (actionComponent != null)? actionComponent : target.GetComponent<IActionComponent>();
}

【讨论】:

  • 关于你的第一句话——我同意,我看不出有什么真正的解决方法。我想我正在钓鱼/祈祷一些可以解决所有问题的奇迹建筑。但我想我们还没有完全实现 ;) 至于工厂方法,我喜欢这样,它至少有助于清理一些东西。我将看看这与我现在所拥有的效果如何。
【解决方案2】:

您说得对,通过强制转换为子类型来测试特定实现并不是一个好主意。从您提供的示例来看,您似乎有一个要对其执行某些操作的IGameObject。与其让您的IActionComponent 公开一个将IGameObject 作为参数的方法,您还可以反过来做,即让IGameObject 采取一个或多个您可以执行的操作.

interface IActionComponent
{
    ExecuteAction();
}

interface IGameObject
{
    IActionComponent ActionComponent { get; }
}

然后在执行方法中做

public void IWantYouToAttackSuperSlow(IGameObject target)
{
    target.ActionComponent.ExecuteAction();
}

根据您实现 IActionComponent 的方式,多态性将确保执行正确的代码。

有一种称为Command Pattern 的模式似乎符合您的需要。

【讨论】:

  • 啊,我在这里提供的案例研究只是我遇到的接口继承问题的一个实例。当我正在开发一个基于组件的系统时,IGameObject只是一个非常简单的组件容器,绝对没有关于任何“真实”组件的逻辑或知识。从本质上讲,它只是一个 GUID,它允许将覆盖给定“实体”的组件绑定到该“实体”(但我已将该概念封装在接口/类中以提供轻松的组件检索)。
  • @Icelight:我不能声称完全了解您的架构。但是,您的句子 “IGameObject 只是一个非常简单的组件容器,绝对没有逻辑..” 对我来说听起来很奇怪。每当我听到“应该只是一组属性,没有任何逻辑”的类时,我都会想起我学到的面向对象编程的第一件事:它是关于将行为 数据在哪里。所以,在我看来,无逻辑类有点代码味道。
  • @sJhonny:我明白你的意思。但是,在这种情况下,为了我的方便而不是需要,放置了一个 GameObject。在一个“真正的”基于组件的游戏系统中,所有组件都将漂浮在庞大的组件云中(或者,更有可能的是,坐在专门针对其组件类型的管理器中),只有类似于 GUID 之类的东西与属于一起到同一个实体。我没有采用这种方法,而是使用一个包含同一实体的所有组件的 GameObject - 方便,因为这种方法对我自己来说更容易可视化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-25
  • 2014-12-22
  • 1970-01-01
相关资源
最近更新 更多