2012-09-11 14 views
6

Me imagino que usaré un caso de estudio de algo en mi código como un ejemplo de lo que quiero decir.Herencia/jerarquías de interfaz en .Net: ¿hay una forma mejor?

Ahora mismo, estoy trabajando en un sistema de comportamiento/acción para mi sistema de juegos basado en componentes. GameObjects pueden tener un IBehaviorComponent y un IActionComponent unido a ellos, que, mostrando sólo los detalles relevantes, exponer lo siguiente:

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

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

Ahora bien, esto es todo muy bien hasta ahora (al menos para mí!). Pero los problemas comienzan a surgir cuando veo las implementaciones de IActionComponent.

Por ejemplo, una implementación simple 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 
} 

Pero, digamos que quiero introducir una implementación IActionComponent más complejo que permite acciones a ejecutar en un horario programado:

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

Y ahora, digamos que quiero exponer IdleTime para que pueda ser modificado por influencias externas. Mis pensamientos en un principio eran crear una nueva interfaz:

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

    float IdleTimeRemaining { get; set; } 
} 

Sin embargo, el problema aquí es que mi sistema de componentes almacena todo en un nivel arriba de IBaseComponent. Entonces, el componente de acción para un GameObject se recupera como un IActionComponent, -not- como, por ejemplo, un ITimedActionComponent, o un IRandomizedActionComponent, o incluso un ICrashTheProgramActionComponent. Espero que las razones sean obvias, ya que quiero que cualquier cosa pueda consultar el GameObject para uno de sus componentes sin tener que saber exactamente qué quiere más allá del tipo básico de componente (IActionComponent, IRenderableComponent, IPhysicsComponent, etc.)

¿Existe una forma más limpia de manejar esto, que me permita exponer estas propiedades definidas en clases secundarias sin que todo tenga que convertir el IActionComponent recuperado al tipo que le interesa? ¿O es simplemente la única/mejor forma de lograr esto? Algo así como:

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

En este momento estoy pensando que es La única manera, pero pensé que había que ver si hay un patrón escondido en la madera que soy ignorante, o si alguien puede sugerir una una forma mucho mejor de lograr esto para empezar.

Gracias!

+0

Mi otro pensamiento era utilizar otro objeto en el interior, como IActionComponentExecutor, lo que haría Decidir cuándo llamar a Action.Execute(), pero creo que eso me lleva de vuelta al principio en una moda de ida y vuelta: los objetos externos tendrán que buscar IActionComponentTimedExecutor (oh ~ estamos llegando a la empresa-y ahora !) para cambiar IdleTime. – Terminal8

+0

Fuera de interés: ¿está el estado de actionComponent.IdleTime almacenado en algún tipo de bolsa genérica? Si es así, puede tener los métodos 'Apply (BagData bag)' y 'Store (BagData bag)' en su interfaz base. Dicho esto, es posible que también deba considerar algún sistema de tipo metadata que pueda usar para consultar propiedades que necesiten conocer los tipos derivados. 'TypeDescriptor' es un punto de inicio listo para ser utilizado por elementos como cuadrículas de propiedades, etc. –

+0

No, actualmente no lo es. Tampoco estoy completamente familiarizado con el concepto. ¿Hay alguna referencia que puedas proporcionar? – Terminal8

Respuesta

0

Tiene razón, no es una buena idea probar una implementación específica mediante conversión a subtipos. De los ejemplos que ha proporcionado, parece que tiene un IGameObject sobre el que desea realizar alguna acción. En lugar de tener su IActionComponent exponiendo un método que toma el IGameObject como argumento, podría hacerlo al revés, es decir, deje que el IGameObject tome una, o muchas si lo prefiere, acciones que puede ejecutar.

interface IActionComponent 
{ 
    ExecuteAction(); 
} 

interface IGameObject 
{ 
    IActionComponent ActionComponent { get; } 
} 

continuación, en el método de ejecución de hacer

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

Dependiendo de cómo se implemente IActionComponent, el polimorfismo se asegurará de que se ejecute el código correcto.

Hay un patrón llamado Command Pattern que parece ajustarse a su necesidad.

+0

Ah, el estudio de caso que proporcioné aquí fue solo una instancia de este problema de herencia de interfaz en el que me estoy ejecutando. A medida que desarrollo un sistema basado en componentes, IGameObject es * solo * un contenedor extremadamente simple para componentes, sin ninguna lógica o conocimiento sobre componentes "reales". En esencia, es solo un GUID que permite que los componentes que cubren una 'entidad' dada estén vinculados a esa 'entidad' (pero he encapsulado ese concepto en una interfaz/clase para facilitar la recuperación de componentes). – Terminal8

+0

@Icelight: No puedo afirmar que entiendo completamente su arquitectura. Sin embargo, su oración * "IGameObject es solo un contenedor extremadamente simple para los componentes, sin ninguna lógica .." * me suena extraño. Cada vez que escucho de una clase que "debería ser solo una bolsa de propiedades, sin ninguna lógica", me acuerdo de lo primero que aprendí sobre la programación orientada a objetos: se trata de poner el ** comportamiento ** donde el ** datos ** es. Entonces, en mi opinión, las clases sin lógica son un poco olor a código. –

+0

@sJhonny: entiendo a lo que te refieres. Sin embargo, en este caso, un GameObject se puso en marcha para mi conveniencia, en lugar de necesitarlo. En un sistema de juego "verdadero" basado en componentes, todos los componentes estarían flotando en la gran nube de componentes grandes (o, más probablemente, sentados en gerentes dedicados a su tipo de componente), con solo algo así como un GUID vinculando a los que pertenecen a la misma entidad juntos. En lugar de adoptar este enfoque, estoy usando un GameObject que contiene todos los componentes de la misma entidad, conveniencia, ya que este enfoque es más fácil de visualizar para mí. – Terminal8

1

El código que mostró que echa abajo una IActionComponent a un ITimedActionComponent es (por lo que puedo ver) inevitable- que tiene que estar al tanto de las propiedades IdleTime con el fin de utilizarlos, ¿verdad?

Creo que el truco aquí sería ocultar ese código no tan bonito en el que las clases que usan tu IActionComponent no tendrán que lidiar con él.
Mi primer pensamiento sobre la manera de hacer esto es utilizar un Factory:

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

y el método de su fábrica:

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>(); 
} 
+0

En lo que respecta a su primera frase, estoy de acuerdo, y no veo ninguna forma real de evitarla. Supongo que estaba pescando/rezando por una arquitectura milagrosa que arreglaría todo. Pero supongo que todavía no hemos llegado;) En cuanto al método Factory, me gusta, y al menos ayudará a limpiar un poco las cosas. Voy a ver qué tan bien funcionará esto con lo que tengo ahora. – Terminal8

Cuestiones relacionadas