2012-09-11 22 views
6

Ich denke, ich verwende eine Fallstudie von etwas in meinem Code als ein Beispiel dafür, was ich meine.Interface Vererbung/Hierarchien in .Net - gibt es einen besseren Weg?

Momentan arbeite ich an einem Verhaltens-/Aktionssystem für mein komponentenbasiertes Spielsystem. Gameobjects kann eine IBehaviorComponent haben und eine IActionComponent mit ihnen verbunden, die nur die relevanten Angaben zu entnehmen sind, setzen die folgenden:

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

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

Nun, dies so weit alles in Ordnung ist (zumindest für mich!). Aber Probleme beginnen zu brauen, wenn ich Implementierungen von IActionComponent ansehe.

Zum Beispiel eine einfache IActionComponent Implementierung:

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 
} 

Aber lassen Sie uns sagen, dass ich eine komplexere IActionComponent Implementierung einführen wollen, die für Aktionen können auf einem zeitlich abgestimmten Zeitplan ausgeführt werden:

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

Und jetzt sagen wir, ich möchte IdleTime offen legen, so dass es durch äußere Einflüsse verändert werden kann. Meine Gedanken waren anfangs eine neue Schnittstelle zu erstellen:

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

    float IdleTimeRemaining { get; set; } 
} 

jedoch hier das Problem ist, dass meine Komponentensystem speichert alles auf einer Ebene-up von IBaseComponent. Die Aktionskomponente für ein GameObject wird also als IActionComponent, -not- abgerufen, z. B. als ITimedActionComponent oder IRandomizedActionComponent oder ICrashTheProgramActionComponent. Ich hoffe, die Gründe dafür sind offensichtlich, da ich möchte, dass das GameObject nach einer seiner Komponenten abgefragt werden kann, ohne genau wissen zu müssen, was es über den Basistyp der Komponente (IActionComponent, IRenderableComponent, IPhysicsComponent, etc.) hinaus möchte.

Gibt es eine sauberere Möglichkeit, dies zu behandeln, die es mir ermöglichen, diese in untergeordneten Klassen definierten Eigenschaften verfügbar zu machen, ohne dass alles die abgerufene IActionComponent zu dem Typ, an dem es interessiert ist, zu werfen? Oder ist das der einzige/beste Weg, dies zu erreichen? Etwas wie:

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

Im Moment ist ich denke, das ist Der einzige Weg, aber ich dachte, ich sehen würde, wenn es ein Muster in dem Holz versteckt ist, dass ich unwissend, oder wenn jemand ein vorschlagen viel bessere Art, dies zu erreichen, um damit zu beginnen.

Danke!

+0

Mein anderer Gedanke war, in ein anderes Objekt zu verwenden, wie IActionComponentExecutor, was würde entscheiden, wann Action.Execute() aufgerufen wird, aber ich denke, dass bringt mich zurück auf Platz eins in einer runden-Art und Weise - externe Objekte müssen dann IActionComponentTimedExecutor (Oh ~ wir werden nun Enterprise-y bekommen) !) um IdleTime zu ändern. – Terminal8

+0

Aus Interesse - ist der Zustand für actionComponent.IdleTime in einer Art generischer Tasche gespeichert? Wenn dies der Fall ist, können Sie die Methoden "Apply (BagData bag)" und "Store (BagData bag)" auf Ihrer Basisschnittstelle anwenden. Das heißt, Sie müssen möglicherweise auch ein System vom Typ Metadaten in Betracht ziehen, mit dem Sie Eigenschaften abfragen können, ohne die abgeleiteten Typen kennen zu müssen. 'TypeDescriptor' ist ein fertig gerollter Startpunkt, der von Dingen wie Eigenschaftenrastern usw. verwendet wird. –

+0

Nein, momentan ist es nicht. Ich kenne das Konzept auch nicht ganz - gibt es einen Hinweis, den Sie vielleicht geben könnten? – Terminal8

Antwort

0

Sie haben Recht, es ist keine gute Idee, für eine bestimmte Implementierung zu testen, indem Sie auf Subtypen umwandeln. Aus den Beispielen, die Sie zur Verfügung gestellt haben, sieht es so aus, als hätten Sie eine IGameObject, an der Sie etwas ausführen möchten. Anstatt Ihre IActionComponent eine Methode zur Verfügung zu stellen, die die IGameObject als Argument nimmt, könnten Sie es anders herum tun, d. H. Lassen Sie die IGameObject eine, oder viele, wenn Sie bevorzugen, Aktionen, die Sie ausführen können.

interface IActionComponent 
{ 
    ExecuteAction(); 
} 

interface IGameObject 
{ 
    IActionComponent ActionComponent { get; } 
} 

dann im Vollstreckungsverfahren zu tun

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

Je nachdem, wie Sie IActionComponent implementieren, wird Polymorphismus stellen Sie sicher, dass der richtige Code ausgeführt wird.

Es gibt ein Muster namens Command Pattern, das Ihren Bedürfnissen entspricht.

+0

Ah, die Fallstudie, die ich hier vorstellte, war nur eine Instanz dieses Interface-Vererbungsproblems, in das ich hineingerannt bin. Da ich ein komponentenbasiertes System entwickle, ist IGameObject * nur * ein extrem einfacher Container für Komponenten, mit absolut keiner Logik oder Wissen über irgendwelche "echten" Komponenten. Im Wesentlichen ist es nur eine GUID, die es ermöglicht, dass Komponenten, die eine gegebene 'Entität' abdecken, mit dieser 'Entität' verbunden sind (aber ich habe dieses Konzept in einer Schnittstelle/Klasse gekapselt, um einen einfachen Komponentenabruf zu ermöglichen). – Terminal8

+0

@Icelight: Ich kann nicht behaupten, deine Architektur vollständig zu verstehen. Allerdings ist Ihr Satz * "IGameObject ist nur ein extrem einfacher Container für Komponenten, mit absolut keiner Logik .." * klingt für mich seltsam. Wann immer ich von einer Klasse höre, die "nur eine Tasche von Eigenschaften sein sollte, ohne jede Logik", werde ich an das Erste erinnert, das ich über objektorientierte Programmierung gelernt habe: Es geht darum, das ** Verhalten ** zu setzen ** Daten ** ist. Meiner Meinung nach sind Logik-lose Klassen ein bisschen wie ein Code-Geruch. –

+0

@sJhonny: Ich verstehe, was Sie bekommen. In diesem Fall wurde jedoch ein GameObject zu meinem Nutzen eingerichtet, anstatt es zu benötigen. In einem "echten" komponentenbasierten Spielsystem würden alle Komponenten in der großen Komponentenwolke herumschweben (oder, wahrscheinlicher, in Managern sitzen, die ihrem Komponententyp gewidmet sind), wobei nur etwas wie eine GUID diejenigen bindet, die dazugehören zu derselben Einheit zusammen. Anstatt diesen Ansatz zu verwenden, verwende ich ein GameObject, das alle Komponenten der gleichen Entität enthält - Bequemlichkeit, da dieser Ansatz für mich einfacher zu visualisieren ist. – Terminal8

1

Der Code zeigte, dass wirft nach unten ein IActionComponent zu einem ITimedActionComponent ist (soweit ich sehen kann) unavoidable- Sie kennen die IdleTime Eigenschaften, um sie zu nutzen zu haben, nicht wahr?

Ich denke, der Trick hier wäre, den nicht so schön aussehenden Code zu verstecken, wo die Klassen, die Ihre IActionComponent verwenden, nicht damit umgehen müssen.
Mein erster Gedanke, wie dies zu tun, ist ein verwenden Factory:

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

und Verfahren Ihrer Fabrik:

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

In Bezug auf Ihren ersten Satz dort - ich stimme zu, und ich kann keinen wirklichen Weg um ihn herum sehen. Ich glaube, ich war am Fischen/betete für eine Wunderarchitektur, die alles reparieren würde. Aber ich denke, wir sind noch nicht ganz da;) Was die Factory-Methode betrifft, mag ich das, und sie wird zumindest dazu beitragen, die Dinge ein bisschen aufzuräumen. Ich werde mir ansehen, wie gut das mit dem, was ich gerade habe, funktionieren wird. – Terminal8

Verwandte Themen