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

I believe that as an example of what I mean, I will use the example from my code.

I am currently working on a behavior / action system for my component-based game system. GameObjects can have an IBehaviorComponent and an IActionComponent attached to it, which, showing only the relevant data, exposes the following:

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

Now everything is fine (at least for me!). But the problem starts with brew when I look at IActionComponent implementations.

For example, a simple implementation of 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 } 

But let's say I want to introduce a more complex implementation of IActionComponent, which allows you to perform scheduled actions:

 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 external 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 problem is that my component system stores everything at the same level from IBaseComponent. Thus, the action component for a GameObject is retrieved as an IActionComponent, not like ITimedActionComponent, or IRandomizedActionComponent, or even ICrashTheProgramActionComponent. I hope the reasons for this are obvious, since I want something to be able to request a GameObject for one of its components, not knowing exactly what it wants, besides the main component type (IActionComponent, IRenderableComponent, IPhysicsComponent, etc.)

Is there a cleaner way to handle this that allows me to expose these properties defined in child classes without having to pass all that was needed to the selected IActionComponent in the type of interest to it? Or is it just the only / best way to achieve this. Sort of:

 public void IWantYouToAttackSuperSlow(IGameObject target) { //Let get the action component for the gameobject and test if it an ITimedActionComponent... ITimedActionComponent actionComponent = target.GetComponent<IActionComponent>() as ITimedActionComponent; if (actionComponent != null) //We're okay! { actionComponent.IdleTime = int.MaxValue; //Mwhaha } } 

Right now, I think the โ€œOnly Wayโ€, but I thought I'd see if there was a pattern lurking in a tree that I didn't know about, or if someone could suggest a much better way to accomplish this, to start with.

Thanks!

+6
source share
2 answers

The code you showed that dropping the IActionComponent in the ITimedActionComponent (as far as I can see) is inevitable - you need to know about the IdleTime properties in order to use them, right?

I think the trick here is to hide this not-so-pretty code where classes using your IActionComponent will not deal with it.
My first thought on how to do this is to use Factory :

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

and your factory 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>(); } 
+1
source

You are right, it is not a good idea to test a specific implementation by dropping subtypes. From the examples you cited, it looks like this: IGameObject , on which you want to perform some action. Instead of IActionComponent your IActionComponent to a method that takes IGameObject as an argument, you can do it the other way around, that is, let IGameObject take one or many, if you like, actions that you can perform.

 interface IActionComponent { ExecuteAction(); } interface IGameObject { IActionComponent ActionComponent { get; } } 

then in the do execution method

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

Depending on how you implement the IActionComponent, polymorphism will ensure that the correct code is executed.

There is a Command Pattern that seems to suit you.

0
source

Source: https://habr.com/ru/post/925173/


All Articles