Une tâche dans ma classe de génie logiciel est de concevoir une application qui peut jouer différentes formes d'un jeu particulier. Le jeu en question est Mancala, certains de ces jeux sont appelés Wari ou Kalah. Ces jeux diffèrent sur certains aspects, mais pour ma question, il est seulement important de savoir que les jeux peuvent différer de la manière suivante:
- La façon dont le résultat d'un déménagement est géré
- La façon dont la fin de la partie est déterminée
- La façon dont le gagnant est déterminé
La première chose qui m'est venue à l'esprit pour concevoir ceci était d'utiliser le modèle de stratégie, j'ai une variation dans les algorithmes (les règles réelles du jeu). Le design pourrait ressembler à ceci:
Je me suis alors dit que dans le jeu de Mancala et Wari, la façon dont le gagnant est déterminé est exactement la même et le code serait dupliqué. Je ne pense pas que ce soit par définition une violation du principe `` une règle, un seul endroit '' ou DRY, car un changement dans les règles pour Mancala ne signifierait pas automatiquement que la règle devrait également être modifiée dans Wari. Néanmoins, grâce aux commentaires de mon professeur, j'ai eu l'impression de trouver un design différent.
J'ai ensuite trouvé ceci:
Chaque jeu (Mancala, Wari, Kalah, ...) aurait juste un attribut du type d'interface de chaque règle, c'est WinnerDeterminer
-à- dire et s'il y a une version Mancala 2.0 qui est la même que Mancala 1.0 sauf pour la façon dont le gagnant est déterminé, il peut simplement utilisez les versions de Mancala.
Je pense que la mise en œuvre de ces règles en tant que modèle de stratégie est certainement valable. Mais le vrai problème vient quand je veux le concevoir davantage.
En lisant sur le modèle de méthode de modèle, j'ai immédiatement pensé qu'il pouvait être appliqué à ce problème. Les actions effectuées lorsqu'un utilisateur effectue un mouvement sont toujours les mêmes et dans le même ordre, à savoir:
- déposer des pierres dans des trous (c'est le même pour tous les jeux, donc serait implémenté dans la méthode du modèle elle-même)
- déterminer le résultat du déménagement
- déterminer si le jeu est terminé à cause du coup précédent
- si le jeu est terminé, déterminez qui a gagné
Ces trois dernières étapes sont toutes dans mon modèle de stratégie décrit ci-dessus. J'ai beaucoup de mal à combiner ces deux. Une solution possible que j'ai trouvée serait d'abandonner le modèle de stratégie et de faire ce qui suit:
Je ne vois pas vraiment la différence de conception entre le modèle de stratégie et celui-ci? Mais je suis certain que je dois utiliser une méthode de modèle (bien que j'étais tout aussi sûr d'avoir à utiliser un modèle de stratégie).
Je ne peux pas non plus déterminer qui serait responsable de la création de l' TurnTemplate
objet, alors qu'avec le modèle de stratégie, j'ai le sentiment d'avoir des familles d'objets (les trois règles) que je pourrais facilement créer en utilisant un modèle d'usine abstrait. Je puis un MancalaRuleFactory
, WariRuleFactory
, etc. , et ils créeraient les instances correcte des règles et la main - moi un RuleSet
objet.
Disons que j'utilise le modèle de stratégie + usine abstraite et que j'ai un RuleSet
objet qui contient des algorithmes pour les trois règles. La seule façon dont je sens que je peux encore utiliser le modèle de méthode de modèle avec ceci est de passer cet RuleSet
objet à mon TurnTemplate
. Le «problème» qui fait alors surface est que je n'aurais jamais besoin de mes implémentations concrètes de la TurnTemplate
, ces classes deviendraient obsolètes. Dans mes méthodes protégées dans le TurnTemplate
je pourrais simplement appeler ruleSet.determineWinner()
. En conséquence, la TurnTemplate
classe ne serait plus abstraite mais devrait devenir concrète, s'agit-il alors encore d'un modèle de méthode modèle?
Pour résumer, est-ce que je pense dans le bon sens ou est-ce que je manque quelque chose de facile? Si je suis sur la bonne voie, comment combiner un modèle de stratégie et un modèle de méthode de modèle?
la source
Réponses:
Après avoir examiné vos conceptions, vos première et troisième itérations semblent être des conceptions plus élégantes. Cependant, vous mentionnez que vous êtes étudiant et votre professeur vous a fait part de vos commentaires. Sans savoir exactement quel est votre devoir ou le but de la classe ou plus d'informations sur ce que votre professeur a suggéré, je prendrais tout ce que je dis ci-dessous avec un grain de sel.
Dans votre premier design, vous déclarez
RuleInterface
être une interface qui définit comment gérer le tour de chaque joueur, comment déterminer si le jeu est terminé et comment déterminer un gagnant après la fin du jeu. Il semble que ce soit une interface valide pour une famille de jeux qui connaissent des variations. Cependant, selon les jeux, vous pourriez avoir du code en double. Je suis d'accord que la flexibilité de changer les règles d'un jeu est une bonne chose, mais je dirais également que la duplication de code est terrible pour les défauts. Si vous copiez / collez du code défectueux entre les implémentations et que l'un contient un bogue, vous avez maintenant plusieurs bogues qui doivent être corrigés à différents emplacements. Si vous réécrivez les implémentations à différents moments, vous pouvez introduire des défauts à différents emplacements. Aucun de ces éléments n'est souhaitable.Votre deuxième design semble assez complexe, avec un arbre d'héritage profond. Au moins, c'est plus profond que ce à quoi je m'attendais pour résoudre ce type de problème. Vous commencez également à diviser les détails de l'implémentation en d'autres classes. En fin de compte, vous modélisez et implémentez un jeu. Cela pourrait être une approche intéressante si vous deviez mélanger et assortir vos règles pour déterminer les résultats d'un coup, la fin de la partie et un gagnant, ce qui ne semble pas correspondre aux exigences que vous avez mentionnées. . Vos jeux sont des ensembles de règles bien définis, et j'essaierais d'encapsuler les jeux autant que possible dans des entités distinctes.
Votre troisième design est celui que j'aime le plus. Ma seule préoccupation est que ce n'est pas au bon niveau d'abstraction. En ce moment, vous semblez modéliser un virage. Je recommanderais d'envisager de concevoir le jeu. Considérez que vous avez des joueurs qui font des mouvements sur une sorte de plateau en utilisant des pierres. Votre jeu nécessite la présence de ces acteurs. À partir de là, votre algorithme n'est pas
doTurn()
maisplayGame()
, qui va du coup initial au coup final, après quoi il se termine. Après le coup de chaque joueur, il ajuste l'état du jeu, détermine si le jeu est dans un état final, et si c'est le cas, détermine le gagnant.Je recommanderais de regarder de plus près vos premier et troisième designs et de travailler avec eux. Cela pourrait aussi aider à penser en termes de prototypes. À quoi ressembleraient les clients qui utilisent ces interfaces? Une approche de conception a-t-elle plus de sens pour implémenter un client qui va réellement instancier un jeu et y jouer? Vous devez comprendre avec quoi il interagit. Dans votre cas particulier, c'est la
Game
classe et tout autre élément associé - vous ne pouvez pas concevoir de manière isolée.Puisque vous mentionnez que vous êtes un étudiant, je voudrais partager quelques éléments d'une époque où j'étais l'AT pour un cours de conception de logiciels:
la source
GameTemplate
qui se sent beaucoup mieux. Cela me permet également de combiner une méthode d'usine pour initialiser les joueurs, le plateau, etc.Votre confusion est justifiée. Le fait est que les modèles ne s'excluent pas mutuellement.
La méthode de modèle est la base d'un tas d'autres modèles, tels que la stratégie et l'état. Essentiellement, l'interface de stratégie contient une ou plusieurs méthodes de modèle, chacune nécessitant que tous les objets implémentant une stratégie aient (au moins) quelque chose comme une méthode doAction (). Cela permet aux stratégies de se substituer les unes aux autres.
En Java, une interface n'est rien d'autre qu'un ensemble de méthodes de modèle. De même, toute méthode abstraite est essentiellement une méthode modèle. Ce modèle (entre autres) était bien connu des concepteurs de la langue, alors ils l'ont intégré.
@ThomasOwens offre d'excellents conseils pour aborder votre problème particulier.
la source
Si vous êtes distrait par les modèles de conception, je vous conseillerais de commencer par créer un prototype du jeu, puis les modèles devraient simplement vous sauter dessus. Je ne pense pas qu'il soit vraiment possible ou conseillé d'essayer de concevoir un système parfaitement en premier, puis de l'implémenter (de la même manière, je trouve cela perplexe lorsque les gens essaient d'abord d'écrire des programmes entiers puis de compiler, plutôt que de le faire petit à petit) .) Le problème est qu'il est peu probable que vous pensiez à chaque scénario auquel votre logique devra faire face, et pendant la phase de mise en œuvre, vous perdrez tout espoir ou essayez de vous en tenir à votre conception défectueuse d'origine et d'introduire des hacks, ou même pire ne livre rien du tout.
la source
Passons aux punaises de laiton. Aucune interface de jeu, aucun modèle de conception, aucune classe abstraite et aucun UML ne sont absolument nécessaires.
Si vous disposez d'un nombre raisonnable de classes de support, telles que l'interface utilisateur, la simulation, etc., pratiquement tout votre code non spécifique à la logique du jeu est de toute façon réutilisé. De plus, votre utilisateur ne change pas son jeu dynamiquement. Vous ne basculez pas à 30 Hz entre les jeux. Vous jouez à un jeu pendant environ une demi-heure. Votre polymorphisme "dynamique" n'est donc pas du tout dynamique. C'est plutôt statique.
Donc, la meilleure façon d'aller ici est d'utiliser une abstraction fonctionnelle générique, comme C #
Action
ou C ++std::function
, de créer une classe Mancala, une Wari et une Kalah, et de partir de là.Terminé.
Vous n'appelez pas Games. Les jeux vous appellent.
la source