2012-09-11 17 views
6

Myślę, że wykorzystam studium przypadku czegoś w moim kodzie jako przykład tego, co mam na myśli.Dziedziczenie interfejsu/hierarchie w .Net - czy istnieje lepszy sposób?

W tej chwili pracuję nad systemem zachowania/działania dla mojego opartego na komponentach systemu gry. GameObjects może mieć IBehaviorComponent oraz IActionComponent dołączony do nich, co pokazano tylko odpowiednie dane, wystawiać następujące:

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

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

Teraz to wszystko jest w porządku tak daleko (przynajmniej dla mnie!). Ale kłopoty zaczynają warzyć, gdy patrzę na implementacje IActionComponent.

Na przykład, prosta implementacja 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 
} 

Ale powiedzmy, że chcemy wprowadzić bardziej kompleksową realizację IActionComponent który pozwala na działania, które mają być wykonywane na czasowym harmonogramu:

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

A teraz, powiedzmy, chcę wystawić IdleTime, aby mógł być zmieniony przez wpływy zewnętrzne. Moje myśli były początkowo, aby utworzyć nowy interfejs:

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

    float IdleTimeRemaining { get; set; } 
} 

Jednak problem jest to, że mój system komponent przechowuje wszystko w jednym-level-up z IBaseComponent. Tak więc składnik akcji dla GameObject jest pobierany jako IActionComponent, -nie-, powiedzmy, ITimedActionComponent, IRandomizedActionComponent, a nawet ICrashTheProgramActionComponent. Mam nadzieję, że przyczyny tego są oczywiste, ponieważ chcę, aby cokolwiek było w stanie przesłać zapytanie do GameObject dla jednego z jego komponentów bez konieczności dokładnego sprawdzenia, czego chce poza podstawowym typem komponentu (IActionComponent, IRenderableComponent, IPhysicsComponent, itp.)

Czy jest to czystszy sposób, który pozwoli mi na ujawnienie tych właściwości zdefiniowanych w klasach podrzędnych, bez konieczności wysyłania pobranego elementu IActionComponent do typu, który jest zainteresowany? Czy jest to po prostu jedyny/najlepszy sposób na osiągnięcie tego. Coś jak:

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

właśnie teraz myślę, że to The Only Way, ale pomyślałem, że zobaczyć, czy jest jakiś wzór ukrywa się w stolarkę, że jestem ignorantem, albo jeśli ktoś może zaproponować o wiele lepszy sposób na osiągnięcie tego celu.

Dzięki!

+0

Moja druga myśl była użyć innego obiektu wewnątrz, jak IActionComponentExecutor, które zdecyduj, kiedy zadzwonić do Action.Execute(), ale myślę, że to doprowadza mnie z powrotem do kwadratu w sposób okrągły - obiekty zewnętrzne będą wtedy musiały wyśledzić IActionComponentTimedExecutor (oh ~ dostaniemy przedsiębiorstwo - y teraz !), aby zmienić IdleTime. – Terminal8

+0

Ciekawe - czy stan akcjiComponent.IdleTime jest przechowywany w jakiejś ogólnej torbie? Jeśli tak, możesz użyć aplikacji 'Apply (BagData bag)' i 'Store (BagData bag)' w twoim podstawowym interfejsie. Powiedział, że być może trzeba również rozważyć system typu metadanych, za pomocą którego można wyszukiwać właściwości wymagające poznania typów pochodnych. 'TypeDescriptor' jest gotowym punktem początkowym używanym przez obiekty takie jak siatki własności itp. –

+0

Nie, obecnie nie jest. Nie jestem do końca zaznajomiony z tym pojęciem - czy istnieje odniesienie, które mógłbyś podać? – Terminal8

Odpowiedz

0

Masz rację, nie jest dobrym pomysłem testowanie określonej implementacji przez rzutowanie na podtypy. Z podanych przykładów wygląda na to, że masz IGameObject, na którym chcesz wykonać pewne działanie. Zamiast tego, aby Twój IActionComponent odsłonił metodę, która przyjmuje IGameObject jako argument, możesz zrobić to w inny sposób, tzn. Pozwolić, aby IGameObject wziął jedną lub wiele, jeśli wolisz, akcji, które możesz wykonać.

interface IActionComponent 
{ 
    ExecuteAction(); 
} 

interface IGameObject 
{ 
    IActionComponent ActionComponent { get; } 
} 

następnie w metodzie wykonującego zrobić

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

W zależności od sposobu wdrożenia IActionComponent, polimorfizm będzie upewnić się, że prawo kod jest wykonywany.

Istnieje wzór o nazwie Command Pattern, który wydaje się pasować do Twoich potrzeb.

+0

Ach, studium przypadku, które tu przedstawiłem, było tylko jednym przykładem tego problemu z dziedziczeniem interfejsu, w którym działam. Ponieważ opracowuję system oparty na komponentach, IGameObject jest * tylko * wyjątkowo prostym kontenerem komponentów, bez żadnej logiki ani wiedzy o żadnych "prawdziwych" komponentach. Zasadniczo jest to po prostu identyfikator GUID, który pozwala na powiązanie komponentów, które obejmują daną jednostkę, z tą "jednostką" (ale ja hermetyzowałem tę koncepcję w interfejsie/klasie, aby zapewnić łatwe pobieranie komponentów). – Terminal8

+0

@Icelight: Nie mogę twierdzić, że w pełni rozumiem swoją architekturę. Jednak twoje zdanie * "Obiekt IGameObject jest tylko bardzo prostym pojemnikiem na komponenty, absolutnie bez logiki .." * brzmi dla mnie dziwnie. Ilekroć słyszę o klasie, która "powinna być tylko workiem właściwości, bez żadnej logiki", przypomina mi się pierwsza rzecz, której nauczyłem się o programowaniu obiektowym: chodzi o wprowadzenie ** zachowania **, gdzie ** dane ** to. Tak więc, moim zdaniem, zajęcia pozbawione logiki są trochę zakodowane w kodzie. –

+0

@sJhonny: Rozumiem, co robisz. Jednak w tym przypadku GameObject został wprowadzony dla mojej wygody, a nie potrzeby. W "prawdziwym" systemie opartym na komponentach wszystkie komponenty byłyby unoszone w wielkiej chmurze składowej (lub, co bardziej prawdopodobne, w menedżerze dedykowanym dla ich typu komponentu), z czymś w rodzaju wiązania GUID, które należy do tego samego podmiotu razem. Zamiast tego podejścia używam GameObject, który przechowuje wszystkie elementy tego samego obiektu - wygodę, ponieważ takie podejście jest łatwiejsze do wizualizacji dla siebie. – Terminal8

1

Kod pokazałeś, że rzuca-dół IActionComponent do ITimedActionComponent jest (o ile można widzę) unavoidable- trzeba zdawać sobie sprawę z właściwości IdleTime w celu wykorzystywania ich, prawda?

Myślę, że najlepszym rozwiązaniem byłoby ukrycie tego niezbyt ładnie wyglądającego kodu, w którym klasy korzystające z Twojego IActionComponent nie będą musiały się z nim obchodzić.
Moja pierwsza myśl o tym, jak to zrobić, to użyć Factory:

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

i sposób swojej fabryki:

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

Jeśli chodzi o twoje pierwsze zdanie, zgadzam się z tym i nie widzę rzeczywistego sposobu. Przypuszczam, że łowiłem ryby/modliłem się o jakąś cudowną architekturę, która naprawi wszystko. Sądzę jednak, że po prostu jeszcze tam nie jesteśmy;) Jeśli chodzi o metodę Factory, to mi się podoba, a przynajmniej pomoże to trochę posprzątać. Zamierzam spojrzeć na to, jak dobrze to będzie działać z tym, co mam teraz. – Terminal8

Powiązane problemy