Czy źle jest mieć wiele zajęć "pracowniczych" (takich jak klasy strategiczne), które robią tylko jedno?Wzorzec strategii i eksplozja klas akcji
Załóżmy, że chcę utworzyć klasę Monster. Zamiast definiować wszystko, co chcę o potworze w jednej klasie, postaram się określić, jakie są jego główne cechy, więc mogę je zdefiniować w interfejsach. Umożliwi to:
- Zamknąć zajęcia, jeśli chcę. Później inni użytkownicy mogą po prostu utworzyć nową klasę i nadal mają polimorfizm za pomocą zdefiniowanych przeze mnie interfejsów. Nie muszę się martwić, jak ludzie (lub ja) mogą chcieć zmienić/dodać funkcje do klasy bazowej w przyszłości. Wszystkie klasy dziedziczą z Object i implementują dziedziczenie poprzez interfejsy, a nie z klas macierzystych.
- Wykorzystaj strategie, których używam z tym potworem dla innych członków mojego świata gry.
Con: Ten model jest sztywny. Czasami chcielibyśmy zdefiniować coś, co nie jest łatwe do osiągnięcia, po prostu próbując złożyć te "klocki".
public class AlienMonster : IWalk, IRun, ISwim, IGrowl {
IWalkStrategy _walkStrategy;
IRunStrategy _runStrategy;
ISwimStrategy _swimStrategy;
IGrowlStrategy _growlStrategy;
public Monster() {
_walkStrategy = new FourFootWalkStrategy();
...etc
}
public void Walk() { _walkStrategy.Walk(); }
...etc
}
Mój pomysł przyniesie serię różnych strategii, które mogą być używane przez różne potwory. Z drugiej strony, niektóre z nich mogłyby być również wykorzystywane do zupełnie innych celów (tj. Mógłbym mieć zbiornik, który również "pływa"). Jedyny problem, jaki widzę z tym podejściem, to to, że może to doprowadzić do eksplozji czystych klas "metod", tj. Klas strategii, które mają tylko jeden cel, do wykonania tego lub innego działania. Z drugiej strony ten rodzaj "modułowości" pozwoliłby na wielokrotne wykorzystywanie strategii, czasami nawet w całkowicie odmiennych kontekstach.
Jaka jest Twoja opinia w tej sprawie? Czy to jest uzasadnione rozumowanie? Czy to nadmierna inżynieria?
Ponadto, zakładając, że chcemy dokonać odpowiednich korekt do przykładu dałem powyżej, byłoby lepiej, aby zdefiniować iWalk jak:
interface IWalk {
void Walk();
}
lub
interface IWalk {
IWalkStrategy WalkStrategy { get; set; } //or something that ressembles this
}
jest, że robi to ja nie musiałbym definiować metod w Monsterze, po prostu miałbym publiczne pozyskiwanie dla IWalkStrategy (wydaje się to sprzeczne z ideą, że powinieneś hermetyzować wszystko tak bardzo, jak tylko możesz!) Dlaczego?
Dzięki
Inną miłą rzeczą w robieniu tego w ten sposób jest to, że kiedy chcesz przejść z punktu A do punktu B, możesz po prostu przejrzeć listę interfejsów lokomocyjnych i poprosić o koszt zamiast o kodowanie "pobierz koszt trasy, zdobądź koszt uruchomienia , uzyskaj koszty pływania, zdobądź koszty przelotu, uzyskaj koszty teleportacji, ... "i poniesiesz techniczny dług, że będziesz musiał aktualizować tę listę w miarę wprowadzania nowych typów lokomocji. –
Czy możesz wyjaśnić, dlaczego Walk, Run i Swim wydają Ci się bardziej podobnymi implementacjami niż interfejsami? Generalnie nie powinien to być model interfejsu, który pewna klasa może "zrobić"? ISwim oznaczałoby, że mój potwór (lub czołg, czy jakakolwiek klasa, którą próbuję wprowadzić) może pływać. Mógłbym wtedy zaimplementować kilka różnych klas strategii (na przykład) takich jak SwimInWater SwimInAcid itp. Czego tu mi brakuje? –
Pozwolę sobie przeformułować "Interfejsy nie opisują, co coś może zrobić per se". Dobrym pomysłem jest używanie interfejsów w logiczny i spójny sposób. Używanie ILocomotion to Growl byłoby złym wyborem. Ale chodzenie, pływanie, bieganie, latanie, teleportowanie itd. To tylko formy ruchu. Przez wyodrębnienie ich do interfejsu ruchu można znacznie uprościć swój kod. Możesz mieć 100 implementacji ILocomotion i być lepszym niż 20 IWalk, 20 IRun, 20 ISwim, 20 ISkip, 2 ITeleport, 18 implementacji IJump. Jeśli coś nie pasuje do interfejsu ILocomotion, zrób nowy interfejs. – Felan