Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interface inheritance/hierarchies in .Net - is there a better way?

I figure I'll use a case study of something in my code as an example of what I mean.

Right now, I'm working on a behavior/action system for my component-based game system. GameObjects can have an IBehaviorComponent and an IActionComponent attached to them, which, showing only the relevant details, expose the following:

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

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

Now, this is all fine so far (at least to me!). But, trouble begins to brew when I look at implementations of IActionComponent.

For example, a simple IActionComponent implementation:

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
}

But, let's say I want to introduce a more complex IActionComponent implementation that allows for actions to be executed on a timed schedule:

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

And now, let's say I want to expose IdleTime so that it can be changed by outside influences. My thoughts at first were to create a new interface:

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

   float IdleTimeRemaining { get; set; }
}

However, the issue here is that my component system stores everything at one-level-up from IBaseComponent. So, the action component for a GameObject is retrieved as an IActionComponent, -not- as, say, an ITimedActionComponent, or an IRandomizedActionComponent, or even an ICrashTheProgramActionComponent. I hope the reasons for this are obvious, as I want anything to be able to query the GameObject for one of it's components without having to know exactly what it wants beyond the basic type of component (IActionComponent, IRenderableComponent, IPhysicsComponent, etc.)

Is there a cleaner way of handling this, that will allow me to expose these properties defined in child classes without everything having to cast the retrieved IActionComponent to the type it's interested in? Or is that simply the only/best way of accomplishing this. Something like:

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

Right now I am thinking that's The Only Way, but I figured I'd see if there's a pattern hiding in the woodwork that I'm ignorant of, or if anyone can suggest a much better way of accomplishing this to begin with.

Thanks!

like image 593
Terminal8 Avatar asked Sep 11 '12 21:09

Terminal8


1 Answers

The code you showed that casts-down an IActionComponent to an ITimedActionComponent is (as far as I can see) unavoidable- you have to be aware of the IdleTime properties in order to use them, right?

I think the trick here would be to hide that not-so-nice-looking code where the classes that use your IActionComponent won't have to deal with it.
My first thought on how to do this is to use a Factory:

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

and your factory's method:

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>();
}
like image 157
J. Ed Avatar answered Sep 28 '22 09:09

J. Ed