Template-Methode mit Strategie kombinieren

14

Eine Aufgabe in meiner Software-Engineering-Klasse ist es, eine Anwendung zu entwerfen, die verschiedene Formen eines bestimmten Spiels spielen kann. Das fragliche Spiel ist Mancala, einige dieser Spiele heißen Wari oder Kalah. Diese Spiele unterscheiden sich in einigen Aspekten, aber für meine Frage ist es nur wichtig zu wissen, dass sich die Spiele in folgenden Punkten unterscheiden können:

  • Die Art und Weise, wie das Ergebnis eines Umzugs behandelt wird
  • Die Art und Weise, in der das Spielende bestimmt wird
  • Die Art und Weise, in der der Gewinner ermittelt wird

Das erste, woran ich dachte, war, das Strategiemuster zu verwenden. Ich habe eine Variation der Algorithmen (die eigentlichen Spielregeln). Das Design könnte so aussehen: Bildbeschreibung hier eingeben

Ich dachte mir dann, dass im Spiel von Mancala und Wari die Art und Weise, wie der Gewinner ermittelt wird, genau dieselbe ist und der Code dupliziert würde. Ich denke nicht, dass dies per definitionem eine Verletzung des Grundsatzes "eine Regel, ein Ort" oder "trocken" ist, da eine Änderung der Regeln für Mancala nicht automatisch bedeutet, dass die Regel auch in Wari geändert werden sollte. Trotzdem hatte ich aufgrund der Rückmeldungen meines Professors den Eindruck, ein anderes Design zu finden.

Ich habe mir dann folgendes ausgedacht: Bildbeschreibung hier eingeben

Jedes Spiel (Mancala, Wari, Kalah, ...) hätte nur ein Attribut des Typs der Schnittstelle jeder Regel, dh WinnerDeterminerund wenn es eine Mancala 2.0-Version gibt, die mit Mancala 1.0 identisch ist, außer wie der Gewinner bestimmt wird, kann dies nur sein Verwenden Sie die Mancala-Versionen.

Ich denke, die Implementierung dieser Regeln als Strategiemuster ist zweifellos gültig. Das eigentliche Problem ist jedoch, dass ich es weiterentwickeln möchte.

Beim Lesen des Schablonenmethodenmusters dachte ich sofort, dass es auf dieses Problem angewendet werden könnte. Die Aktionen, die ausgeführt werden, wenn ein Benutzer einen Zug ausführt, sind immer gleich und in derselben Reihenfolge:

  • Steine ​​in Löcher ablegen (dies ist für alle Spiele gleich, würde also in der Template-Methode selbst implementiert werden)
  • Ermitteln Sie das Ergebnis des Umzugs
  • Ermitteln Sie, ob das Spiel aufgrund des vorherigen Zugs beendet wurde
  • Wenn das Spiel beendet ist, bestimmen Sie, wer gewonnen hat

Diese drei letzten Schritte sind alle in meinem oben beschriebenen Strategiemuster enthalten. Ich habe große Probleme, diese beiden zu kombinieren. Eine mögliche Lösung, die ich gefunden habe, wäre, das Strategiemuster aufzugeben und Folgendes zu tun: Bildbeschreibung hier eingeben

Ich sehe nicht wirklich den Designunterschied zwischen dem Strategiemuster und diesem? Ich bin mir aber sicher, dass ich eine Template-Methode verwenden muss (obwohl ich mir genauso sicher war, ein Strategiemuster verwenden zu müssen).

Ich kann auch nicht bestimmen, wer für die Erstellung des TurnTemplateObjekts verantwortlich ist, während ich mit dem Strategiemuster das Gefühl habe, dass ich Objektfamilien (die drei Regeln) habe, die ich leicht mit einem abstrakten Factory-Muster erstellen könnte. Ich hätte dann ein MancalaRuleFactory, WariRuleFactoryusw. und sie würden die richtigen Instanzen der Regeln erstellen und mir ein RuleSetObjekt zurückgeben.

Angenommen, ich verwende die Strategie + abstraktes Factory-Muster und habe ein RuleSetObjekt, das Algorithmen für die drei Regeln enthält. Ich habe nur das Gefühl, dass ich das Muster der Template-Methode weiterhin verwenden kann, indem ich dieses RuleSetObjekt an meine übergebe TurnTemplate. Das "Problem", das dann auftaucht, ist, dass ich meine konkreten Implementierungen von den TurnTemplate, diesen Klassen niemals brauchen würde, obsolet werden würde. Bei meinen geschützten Methoden TurnTemplatekonnte ich das einfach aufrufen ruleSet.determineWinner(). In der Folge TurnTemplatewäre die Klasse nicht mehr abstrakt, sondern müsste konkretisiert werden, ist sie dann noch ein Template-Methodenmuster?

Fassen wir zusammen: Denke ich richtig oder fehle ich etwas Leichtes? Wie kann ich ein Strategie- und ein Vorlagenmethodenmuster kombinieren, wenn ich auf dem richtigen Weg bin?

Mekswoll
quelle
1
Ich würde sagen, Ihr Denken ist ziemlich genau richtig. Wenn das Feedback, das Sie von Ihrem Professor erhalten, dies nicht bestätigt, bitten Sie ihn, auf die Mängel hinzuweisen, oder geben Sie in der Antwort einen Hinweis darauf, wonach er sucht. Hör auf zu versuchen, Gedanken zu lesen. Wenn Sie davon ausgehen, was Ihr Professor (und später Ihre Benutzer) wollen, ist dies möglicherweise das Schlimmste, was Sie tun können.
Marjan Venema
" ... im Spiel von Mancala und Wari ist die Art und Weise, wie der Gewinner ermittelt wird, genau die gleiche und der Code würde dupliziert. " Warum bedeutet das, dass der Code dupliziert wird? Lassen Sie einfach beide Klassen dieselbe Funktion aufrufen.
D Drmmr

Antworten:

6

Nachdem Sie sich Ihre Entwürfe angesehen haben, erscheinen sowohl die erste als auch die dritte Iteration als elegantere Entwürfe. Sie erwähnen jedoch, dass Sie ein Student sind und Ihr Professor Ihnen Feedback gegeben hat. Ohne genau zu wissen, was Ihre Aufgabe oder der Zweck der Klasse ist, oder mehr darüber, was Ihr Professor vorgeschlagen hat, würde ich alles, was ich unten sage, mit einem Körnchen Salz nehmen.

In Ihrem ersten Entwurf erklären Sie RuleInterface, dass Sie eine Benutzeroberfläche sind, die definiert, wie der Zug jedes Spielers behandelt wird, wie festgestellt wird, ob das Spiel beendet ist und wie ein Gewinner nach dem Ende des Spiels ermittelt wird. Es scheint, dass dies eine gültige Schnittstelle zu einer Familie von Spielen ist, die Variationen erfahren. Abhängig von den Spielen haben Sie möglicherweise Code dupliziert. Ich würde zustimmen, dass die Flexibilität, die Regeln eines Spiels zu ändern, eine gute Sache ist, aber ich würde auch argumentieren, dass die Vervielfältigung von Code fürchterlich für Mängel ist. Wenn Sie fehlerhaften Code zwischen Implementierungen kopieren / einfügen und einer einen Fehler enthält, müssen Sie jetzt mehrere Fehler an verschiedenen Stellen beheben. Wenn Sie die Implementierungen zu unterschiedlichen Zeiten umschreiben, können an verschiedenen Stellen Fehler auftreten. Beides ist nicht wünschenswert.

Ihr zweites Design wirkt recht komplex mit einem tiefen Vererbungsbaum. Zumindest ist es tiefer, als ich erwarten würde, um diese Art von Problem zu lösen. Sie beginnen auch, Implementierungsdetails in andere Klassen aufzuteilen. Letztendlich modellieren und implementieren Sie ein Spiel. Dies könnte ein interessanter Ansatz sein, wenn Sie aufgefordert werden, Ihre Regeln zu kombinieren, um die Ergebnisse eines Zuges, das Spielende und einen Sieger zu ermitteln, der nicht den von Ihnen genannten Anforderungen entspricht . Ihre Spiele sind klar definierte Regelwerke, und ich würde versuchen, die Spiele so weit wie möglich in separate Einheiten zu unterteilen.

Ihr dritter Entwurf gefällt mir am besten. Meine einzige Sorge ist, dass es nicht auf der richtigen Abstraktionsebene ist. Im Moment scheinen Sie eine Wende zu modellieren. Ich würde empfehlen, über das Design des Spiels nachzudenken. Denken Sie daran, dass Sie Spieler haben, die mit Steinen Züge auf einem Brett machen. Für Ihr Spiel müssen diese Schauspieler anwesend sein. Von dort ist der Algorithmus nicht doTurn()aber playGame(), die von dem anfänglichen Schritt zum endgültigen Umzug geht, nach dem es endet. Nach jedem Zug des Spielers wird der Status des Spiels angepasst, es wird festgestellt, ob sich das Spiel in einem endgültigen Status befindet, und wenn dies der Fall ist, wird der Gewinner ermittelt.

Ich würde empfehlen, Ihre ersten und dritten Entwürfe näher zu betrachten und mit ihnen zu arbeiten. Es könnte auch hilfreich sein, in Prototypen zu denken. Wie würden die Clients aussehen, die diese Schnittstellen verwenden? Ist ein Entwurfsansatz sinnvoller für die Implementierung eines Clients, der tatsächlich ein Spiel instanziiert und das Spiel spielt? Sie müssen erkennen, womit es interagiert. In Ihrem speziellen Fall handelt es sich um die GameKlasse und alle anderen zugehörigen Elemente. Sie können nicht isoliert entwerfen.


Da Sie erwähnen, dass Sie ein Student sind, möchte ich einige Dinge aus einer Zeit mitteilen, als ich TA für einen Software-Design-Kurs war:

  • Muster sind einfach eine Möglichkeit, Dinge zu erfassen, die in der Vergangenheit funktioniert haben, aber sie so zu abstrahieren, dass sie in anderen Designs verwendet werden können. Jeder Katalog von Entwurfsmustern gibt einem Muster einen Namen, erläutert seine Absichten und Einsatzmöglichkeiten sowie Situationen, in denen Ihr Entwurf letztendlich eingeschränkt wird.
  • Design kommt mit Erfahrung. Der beste Weg, gutes Design zu erlangen, besteht nicht darin, sich nur auf die Modellierungsaspekte zu konzentrieren, sondern zu erkennen, was in die Implementierung dieses Modells einfließt. Das eleganteste Design ist nützlich, wenn es nicht einfach zu implementieren ist oder nicht in das größere Design des Systems oder in andere Systeme passt.
  • Sehr wenige Designs sind "richtig" oder "falsch". Solange das Design die Anforderungen des Systems erfüllt, kann es nicht falsch sein. Sobald eine Zuordnung von jeder Anforderung zu einer Darstellung vorliegt, wie das System diese Anforderung erfüllen wird, kann das Design nicht falsch sein. An diesem Punkt geht es nur qualitativ um Konzepte wie Flexibilität oder Wiederverwendbarkeit oder Testbarkeit oder Wartbarkeit.
Thomas Owens
quelle
Vielen Dank für die tollen Ratschläge, insbesondere für Ihren Hinweis darauf, dass die Abstraktionsebene falsch war. Ich habe es jetzt in ein geändert, GameTemplatedas sich viel besser anfühlt. Außerdem kann ich eine Factory-Methode kombinieren, um Spieler, das Board usw. zu initialisieren.
Mekswoll,
3

Ihre Verwirrung ist berechtigt. Die Sache ist, dass sich Muster nicht gegenseitig ausschließen.

Die Template-Methode ist die Basis für eine Reihe anderer Muster, wie z. B. Strategie und Status. Grundsätzlich enthält die Strategy-Schnittstelle eine oder mehrere Template-Methoden, für die alle Objekte, die eine Strategie implementieren, (mindestens) so etwas wie eine doAction () -Methode haben müssen. Dadurch können die Strategien gegeneinander ausgetauscht werden.

In Java ist eine Schnittstelle nichts anderes als eine Reihe von Vorlagenmethoden. Ebenso ist jede abstrakte Methode im Wesentlichen eine Template-Methode. Dieses Muster war (unter anderem) den Designern der Sprache gut bekannt, also bauten sie es ein.

@ThomasOwens bietet exzellente Ratschläge zur Lösung Ihres speziellen Problems.

Matthew Flynn
quelle
0

Wenn Sie von Designmustern abgelenkt werden, würde ich Ihnen raten, zuerst den Prototyp des Spiels zu erstellen, und dann sollten Muster einfach auf Sie losgehen. Ich denke nicht, dass es wirklich möglich oder ratsam ist, zuerst zu versuchen, ein System perfekt zu entwerfen und es dann zu implementieren (auf ähnliche Weise finde ich es verwirrend, wenn Leute versuchen, zuerst ganze Programme zu schreiben und dann zu kompilieren, anstatt es Stück für Stück zu tun Das Problem ist, dass Sie wahrscheinlich nicht an jedes Szenario denken, mit dem sich Ihre Logik befassen muss, und dass Sie während der Implementierungsphase entweder die Hoffnung verlieren oder versuchen, an Ihrem ursprünglichen fehlerhaften Design festzuhalten und Hacks einzuführen oder sogar Schlimmer noch, gib gar nichts ab.

James
quelle
2
Obwohl ich Ihnen im Allgemeinen zustimme , ist dies keine wirkliche Antwort, die das Problem des OP löst. "Tu das nicht" ist eine Antwort, aber eine ziemlich schlechte, wenn sie keine brauchbare Alternative darstellt.
Yannis
@YannisRizos Und obwohl ich Ihnen ebenfalls zustimme , denke ich, dass er fundierte Ratschläge gegeben hat (oder Einblicke, wenn Ratschläge nicht das richtige Wort sind). Aus diesem Grund +1. Auch wenn ja, es ist technisch keine sehr hilfreiche Antwort.
gekauft777
-1

Kommen wir zu den Messingnägeln. Es ist absolut keine Spieloberfläche, keine Entwurfsmuster, keine abstrakten Klassen und keine UML erforderlich.

Wenn Sie eine angemessene Anzahl von Support-Klassen haben, wie z. B. Benutzeroberfläche, Simulation und was auch immer, wird der gesamte Code, der nicht für die Spielelogik spezifisch ist, sowieso wiederverwendet. Außerdem ändert Ihr Benutzer sein Spiel nicht dynamisch. Zwischen den Spielen wird nicht mit 30 Hz gewechselt. Sie spielen eine halbe Stunde lang ein Spiel. Ihr "dynamischer" Polymorphismus ist also überhaupt nicht dynamisch. Es ist eher statisch.

Der vernünftige Weg hierher ist es, eine generische funktionale Abstraktion wie C # Actionoder C ++ zu verwenden std::function, eine Mancala-, eine Wari- und eine Kalah-Klasse zu erstellen und von dort fortzufahren.

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

Erledigt.

Sie nennen Spiele nicht. Spiele rufen dich an.

DeadMG
quelle
3
Ich verstehe nicht, wie hilfreich das überhaupt ist. Die Person, die diese Frage stellt, ist ein Student. Er fragt explizit nach Gestaltungsprinzipien und den Wechselwirkungen zwischen zwei Gestaltungsmustern. Zu behaupten, dass Designmuster oder UML nicht erforderlich sind, könnte in einigen Fällen in der Industrie zutreffen, aber das Nachdenken über Designmodelle, Designmuster und UML könnte der Punkt sein, nach dem gefragt wird.
Thomas Owens
3
Zu ihnen gibt es nichts zu sagen, weil sie nirgends gelten. Es macht keinen Sinn, sich Gedanken darüber zu machen, wie Sie das Design mithilfe von Designmustern vollständig optimieren können, es sei denn, Sie möchten lernen, wie man sie nicht verwendet.
DeadMG
4
Es gibt einen besorgniserregenden Trend in SoftDev, bei dem die verwendeten Entwurfsmuster als wichtiger angesehen werden, als nur das Problem zu lösen. Es gibt so etwas wie Überentwicklung.
James
2
@DeadMG: Während Sie beide im Wesentlichen Recht haben, besteht im Fall von OP das Problem darin, die Prüfung zu bestehen, und die Lösung für dieses Problem besteht darin, Entwurfsmuster und UML zu verwenden. Egal wie richtig wir sind, wenn sein Professor mit seinen Lösungen nicht einverstanden ist, wird er nicht bestehen.
Goran Jovic
2
Mein ganzes Leben habe ich immer gedacht, es sei "Messingsteuer" ... Wow, "Messingnägel" sind viel sinnvoller. lol
gekauft777