Łączenie metody szablonu ze strategią

14

Zadaniem w mojej klasie inżynierii oprogramowania jest zaprojektowanie aplikacji, która może grać w różne formy konkretnej gry. Ta gra to Mancala, niektóre z nich nazywane są Wari lub Kalah. Te gry różnią się w niektórych aspektach, ale na moje pytanie ważne jest, aby wiedzieć, że gry mogą się różnić w następujący sposób:

  • Sposób, w jaki obsługiwany jest wynik ruchu
  • Sposób określania końca gry
  • Sposób określania zwycięzcy

Pierwszą rzeczą, jaka przyszła mi do głowy, aby to zaprojektować, było użycie wzorca strategii, mam różne algorytmy (rzeczywiste zasady gry). Projekt może wyglądać następująco: wprowadź opis zdjęcia tutaj

Pomyślałem wtedy, że w grze Mancala i Wari sposób określania zwycięzcy jest dokładnie taki sam, a kod byłby duplikowany. Nie sądzę, że jest to z definicji naruszenie zasady „jedna zasada, jedno miejsce” lub zasada SUCHA postrzegana jako zmiana zasad dla Mancala nie oznacza automatycznie, że zasada ta powinna zostać również zmieniona w Wari. Niemniej jednak dzięki opiniom otrzymanym od mojego profesora miałem wrażenie, że znalazłem inny projekt.

Potem wymyśliłem: wprowadź opis zdjęcia tutaj

Każda gra (Mancala, Wari, Kalah, ...) miałaby po prostu atrybut typu interfejsu każdej reguły, tj. WinnerDeterminerI jeśli istnieje wersja Mancala 2.0, która jest taka sama jak Mancala 1.0, z wyjątkiem tego, w jaki sposób zostanie wyłoniony zwycięzca, może po prostu użyj wersji Mancala.

Myślę, że wdrożenie tych zasad jako wzorca strategii jest z pewnością prawidłowe. Ale prawdziwy problem pojawia się, gdy chcę go dalej projektować.

Czytając o wzorcu metody szablonu, od razu pomyślałem, że można go zastosować do tego problemu. Czynności wykonywane, gdy użytkownik wykonuje ruch, są zawsze takie same i w tej samej kolejności, a mianowicie:

  • osadzaj kamienie w dołkach (jest to takie samo dla wszystkich gier, więc byłoby zaimplementowane w samej metodzie szablonu)
  • określić wynik ruchu
  • ustalić, czy gra zakończyła się z powodu poprzedniego ruchu
  • jeśli gra się zakończy, określ, kto wygrał

Te trzy ostatnie kroki są zgodne ze schematem mojej strategii opisanym powyżej. Mam problem z połączeniem tych dwóch. Jednym z możliwych rozwiązań, które znalazłem, byłoby porzucenie wzorca strategii i wykonanie następujących czynności: wprowadź opis zdjęcia tutaj

Naprawdę nie widzę różnicy w projekcie między wzorem strategii a tym? Ale jestem pewien, że muszę użyć metody szablonu (chociaż byłem równie pewien, że muszę użyć wzorca strategii).

Nie potrafię też ustalić, kto byłby odpowiedzialny za utworzenie TurnTemplateobiektu, podczas gdy przy wzorcu strategicznym czuję, że mam rodziny obiektów (trzy reguły), które można łatwo utworzyć za pomocą abstrakcyjnego wzorca fabrycznego. Chciałbym mieć wtedy MancalaRuleFactory, WariRuleFactoryitp a oni stworzyć odpowiednie instancje zasad i ręce mi się RuleSetobiekt.

Powiedzmy, że używam strategii + abstrakcyjnego wzorca fabrycznego i mam RuleSetobiekt, który ma algorytmy dla trzech reguł. Jedynym sposobem, w jaki mogę nadal używać wzorca metody szablonu w tym celu, jest przekazanie tego RuleSetobiektu do mojego TurnTemplate. „Problem”, który się wtedy pojawia, polega na tym, że nigdy nie potrzebowałbym moich konkretnych implementacji TurnTemplate, klasy te stałyby się przestarzałe. W moich chronionych metodach TurnTemplatemogłem po prostu wywołać ruleSet.determineWinner(). W rezultacie TurnTemplateklasa nie byłaby już abstrakcyjna, ale musiałaby stać się konkretna, czy w takim razie nadal jest wzorcem metody szablonu?

Podsumowując, czy myślę we właściwy sposób, czy brakuje mi czegoś łatwego? Jeśli jestem na dobrej drodze, jak połączyć wzorzec strategii i wzorzec metody szablonu?

Mekswoll
źródło
1
Powiedziałbym, że twoje myślenie jest bardzo trafne. Jeśli informacje zwrotne otrzymane od twojego profesora nie potwierdzają tego, poproś go / nią o wskazanie wad lub podpowiedź, czego szuka w odpowiedzi. Przestańcie próbować czytać w myślach. Zakładanie rzeczy o tym, czego chce twój profesor (a później twoi użytkownicy), jest prawdopodobnie najgorsze, co możesz zrobić.
Marjan Venema
... w grze Mancala i Wari sposób określania zwycięzcy jest dokładnie taki sam, a kod byłby duplikowany. ” Dlaczego to oznacza, że ​​kod jest duplikowany? Niech obie klasy wywołują tę samą funkcję.
D Drmmr

Odpowiedzi:

6

Po spojrzeniu na projekty zarówno pierwsza, jak i trzecia iteracja wydają się być bardziej eleganckie. Wspominasz jednak, że jesteś studentem, a profesor przekazał ci informacje zwrotne. Nie wiedząc dokładnie, jakie jest twoje zadanie lub cel zajęć, ani więcej informacji na temat tego, co sugerował twój profesor, wezmę wszystko, co powiem poniżej, z odrobiną soli.

W swoim pierwszym projekcie deklarujesz, że jesteś RuleInterfaceinterfejsem, który określa, jak poradzić sobie z turą każdego gracza, jak ustalić, czy gra się skończyła, i jak określić zwycięzcę po zakończeniu gry. Wygląda na to, że jest to poprawny interfejs dla rodziny gier, w których występują różnice. Jednak w zależności od gier być może zduplikowano kod. Zgadzam się, że elastyczność zmiany zasad jednej gry jest dobrą rzeczą, ale twierdzę również, że powielanie kodu jest straszne z punktu widzenia wad. Jeśli skopiujesz / wkleisz uszkodzony kod między implementacjami, a jeden zawiera błąd, masz teraz wiele błędów, które należy naprawić w różnych lokalizacjach. Jeśli przepiszesz implementacje w różnych momentach, możesz wprowadzić defekty w różnych lokalizacjach. Żadne z nich nie jest pożądane.

Drugi projekt wydaje się dość złożony, z głębokim drzewem dziedziczenia. Przynajmniej jest głębszy niż się spodziewałbym po rozwiązaniu tego rodzaju problemu. Zaczynasz również rozbijać szczegóły implementacji na inne klasy. Ostatecznie modelujesz i wdrażasz grę. To może być interesujące podejście, jeśli musisz wymieszać i dopasować swoje zasady określania wyników ruchu, końca gry i zwycięzcy, które nie wydają się spełniać wymagań, o których wspomniałeś . Wasze gry są dobrze zdefiniowanymi zestawami reguł, a ja postaram się je jak najściślej podzielić na osobne byty.

Twój trzeci projekt najbardziej mi się podoba. Moją jedyną obawą jest to, że nie jest na odpowiednim poziomie abstrakcji. W tej chwili wydajesz się modelować turę. Poleciłbym rozważyć zaprojektowanie gry. Weź pod uwagę, że masz graczy, którzy wykonują ruchy na jakiejś planszy, używając kamieni. Twoja gra wymaga obecności tych aktorów. Stamtąd twój algorytm nie jest, doTurn()ale playGame(), który przechodzi od pierwszego ruchu do ostatniego ruchu, po którym się kończy. Po każdym ruchu gracza dostosowuje stan gry, określa, czy gra jest w stanie końcowym, a jeśli tak, określa zwycięzcę.

Poleciłbym przyjrzeć się bliżej swojemu pierwszemu i trzeciemu projektowi i pracować z nimi. Pomóc może również myślenie w kategoriach prototypów. Jak wyglądają klienci korzystający z tych interfejsów? Czy jedno podejście do projektowania ma sens w przypadku wdrażania klienta, który faktycznie utworzy instancję gry i w nią zagra? Musisz zdać sobie sprawę z tego, z czym wchodzi w interakcje. W twoim konkretnym przypadku jest to Gameklasa i wszelkie inne powiązane elementy - nie możesz projektować osobno.


Ponieważ wspominasz, że jesteś studentem, chciałbym podzielić się kilkoma rzeczami z czasów, gdy byłem inżynierem TA na kurs projektowania oprogramowania:

  • Wzory to po prostu sposób na uchwycenie rzeczy, które działały w przeszłości, ale ich abstrakcja do punktu, w którym można je wykorzystać w innych projektach. Każdy katalog wzorów projektowych nadaje nazwę wzorowi, wyjaśnia jego intencje i miejsce, w którym można go użyć, oraz sytuacje, w których ostatecznie ograniczyłby twój projekt.
  • Design ma doświadczenie. Najlepszym sposobem na uzyskanie dobrego projektu jest nie tylko skupienie się na aspektach modelowania, ale uświadomienie sobie, co wchodzi w skład tego modelu. Najbardziej elegancki projekt jest przydatny, jeśli nie można go łatwo wdrożyć lub nie pasuje do większego projektu systemu lub innych systemów.
  • Bardzo niewiele projektów jest „dobrych” lub „złych”. Dopóki projekt spełnia wymagania systemu, nie może być źle. Po mapowaniu każdego wymagania na reprezentację tego, jak system spełni to wymaganie, projekt nie może być zły. W tym momencie jest to tylko jakościowy opis takich pojęć, jak elastyczność lub możliwość ponownego użycia, testowalność lub łatwość konserwacji.
Thomas Owens
źródło
Dziękuję za wspaniałą radę, a zwłaszcza twój punkt widzenia na temat tego, że był na złym poziomie abstrakcji. Zmieniłem to teraz na coś, GameTemplateco jest o wiele lepsze. Pozwala mi także łączyć fabryczną metodę inicjalizacji graczy, planszy itp.
Mekswoll,
3

Twoje zamieszanie jest uzasadnione. Chodzi o to, że wzory nie wykluczają się wzajemnie.

Metoda szablonów jest podstawą wielu innych wzorców, takich jak Strategia i Stan. Zasadniczo interfejs strategii zawiera jedną lub więcej metod szablonów, z których każda wymaga, aby wszystkie obiekty implementujące strategię miały (przynajmniej) coś w rodzaju metody doAction (). Pozwala to na wzajemne zastępowanie strategii.

W Javie interfejs jest niczym innym jak zestawem metod szablonów. Podobnie każda metoda abstrakcyjna jest zasadniczo metodą szablonową. Ten wzór (między innymi) był dobrze znany projektantom języka, więc go wbudowali.

@ThomasOwens oferuje doskonałe porady dotyczące podejścia do konkretnego problemu.

Matthew Flynn
źródło
0

Jeśli rozpraszają Cię wzorce projektowe, radzę najpierw stworzyć prototyp gry, a wzorce powinny po prostu wyskoczyć na Ciebie. Nie sądzę, że naprawdę możliwe lub wskazane jest, aby najpierw spróbować zaprojektować system, a następnie go zaimplementować (w podobny sposób wprawia mnie to w zakłopotanie, gdy ludzie próbują najpierw napisać całe programy, a następnie skompilować, zamiast robić to krok po kroku .) Problem polega na tym, że nie jest prawdopodobne, abyś pomyślał o każdym scenariuszu, z którym logika będzie musiała sobie poradzić, a podczas fazy implementacji albo stracisz wszelką nadzieję, albo spróbujesz trzymać się swojego pierwotnego wadliwego projektu i wprowadzić hacki, a nawet gorzej nic nie dostarczają.

James
źródło
2
Chociaż ogólnie zgadzam się z tobą , to nie jest tak naprawdę odpowiedź, która rozwiązuje problem PO. „Nie rób tego” jest odpowiedzią, ale raczej słabą, jeśli nie stanowi realnej alternatywy.
yannis
@YannisRizos I chociaż ja również się z tobą zgadzam , nadal uważam, że dał dobrą radę (lub wgląd, jeśli rada nie jest właściwym słowem). Z tego powodu +1. Mimo to tak, technicznie nie jest to bardzo pomocna odpowiedź.
kupił777
-1

Przejdźmy do mosiężnych haczyków. Nie ma absolutnie żadnej potrzeby jakiegokolwiek interfejsu gry, żadnych wzorców projektowych, żadnych klas abstrakcyjnych i żadnego UML.

Jeśli masz rozsądną liczbę klas wsparcia, takich jak interfejs użytkownika, symulacja i cokolwiek, to w zasadzie cały kod niezwiązany z logiką gry jest ponownie wykorzystywany. Co więcej, użytkownik nie zmienia swojej gry dynamicznie. Nie zmieniasz częstotliwości 30 Hz między grami. Grasz jedną grę przez około pół godziny. Zatem twój „dynamiczny” polimorfizm wcale nie jest dynamiczny. Jest raczej statyczny.

Tak więc rozsądnym sposobem na przejście tutaj jest użycie ogólnej abstrakcyjnej funkcjonalności, takiej jak C # Actionlub C ++ std::function, utworzenie jednej klasy Mancala, jednej Wari i jednej klasy Kalah i przejście od tego miejsca.

std::unordered_map<std::string, std::function<void()>> games = {
    { "Kalah", [] { return Kalah(); } },
    { "Mancala", [] { return Mancala(); } },
    { "Wari", [] { return Wari(); } }
};
void play() {
    std::function<void()> f;
    f = games[GetChoiceFromUI()];
    f();
    if (PlayAgain()) return play();
}
int main() {
    play();
}

Gotowy.

Nie dzwonisz do gier. Gry do ciebie dzwonią.

DeadMG
źródło
3
Nie rozumiem, jak to jest w ogóle pomocne. Osoba zadająca to pytanie jest studentem. On wyraźnie pyta o zasady projektowania i interakcje między dwoma wzorami projektowymi. Mówienie, że nie ma potrzeby tworzenia wzorców projektowych lub UML, może być prawdą w niektórych przypadkach w branży, ale myślenie o modelach projektowych, wzorcach projektowych i UML może być celem tego pytania.
Thomas Owens
3
Nie ma nic do powiedzenia na ich temat, ponieważ nie mają one zastosowania. Nie ma sensu tracić czasu na myślenie o tym, jak całkowicie uprościć projektowanie za pomocą wzorców projektowych, chyba że zamierzasz uczyć się, jak ich nie używać.
DeadMG
4
W SoftDev występuje niepokojący / denerwujący trend, w którym stosowane wzorce projektowe są uważane za ważniejsze niż rozwiązywanie problemu. Istnieje coś takiego jak nadmierna inżynieria.
James
2
@DeadMG: podczas gdy oboje macie rację, w przypadku OP problemem jest zdanie egzaminu, a rozwiązaniem tego problemu jest użycie wzorców projektowych i UML. Bez względu na to, jak słuszni jesteśmy, jeśli jego profesor nie zgodzi się z jego rozwiązaniami, nie przejdzie.
Goran Jovic
2
Moje całe życie ja zawsze myślałem, że to „podatek mosiądz” ... Wow, „pinezki” mosiądz sprawia o wiele więcej sensu. lol
kupił777