Combinando método de plantilla con estrategia

14

Una tarea en mi clase de ingeniería de software es diseñar una aplicación que pueda jugar diferentes formas en un juego en particular. El juego en cuestión es Mancala, algunos de estos juegos se llaman Wari o Kalah. Estos juegos difieren en algunos aspectos, pero para mi pregunta solo es importante saber que los juegos podrían diferir en lo siguiente:

  • La forma en que se maneja el resultado de un movimiento
  • La forma en que se determina el final del juego
  • La forma en que se determina el ganador

Lo primero que se me ocurrió al diseñar esto fue usar el patrón de estrategia, tengo una variación en los algoritmos (las reglas reales del juego). El diseño podría verse así: ingrese la descripción de la imagen aquí

Entonces pensé para mí mismo que en el juego de Mancala y Wari la forma en que se determina el ganador es exactamente la misma y el código se duplicaría. No creo que esto sea, por definición, una violación de la 'regla única, un lugar' o el principio DRY, ya que un cambio en las reglas para Mancala no significaría automáticamente que esa regla deba cambiarse también en Wari. Sin embargo, por los comentarios que recibí de mi profesor, tuve la impresión de encontrar un diseño diferente.

Entonces se me ocurrió esto: ingrese la descripción de la imagen aquí

Cada juego (Mancala, Wari, Kalah, ...) solo tendría un atributo del tipo de interfaz de cada regla, es decir, WinnerDeterminery si hay una versión de Mancala 2.0 que sea igual a Mancala 1.0, excepto cómo se determina el ganador, puede usa las versiones de Mancala.

Creo que la implementación de estas reglas como patrón de estrategia es ciertamente válida. Pero el verdadero problema viene cuando quiero diseñarlo más.

Al leer sobre el patrón de método de plantilla, inmediatamente pensé que podría aplicarse a este problema. Las acciones que se realizan cuando un usuario realiza un movimiento son siempre las mismas y en el mismo orden, a saber:

  • depositar piedras en los agujeros (esto es lo mismo para todos los juegos, por lo que se implementaría en el método de la plantilla en sí)
  • determinar el resultado del movimiento
  • determinar si el juego ha terminado debido al movimiento anterior
  • si el juego ha terminado, determina quién ganó

Esos tres últimos pasos están todos en mi patrón de estrategia descrito anteriormente. Tengo muchos problemas para combinar estos dos. Una posible solución que encontré sería abandonar el patrón de estrategia y hacer lo siguiente: ingrese la descripción de la imagen aquí

¿Realmente no veo la diferencia de diseño entre el patrón de estrategia y esto? Pero estoy seguro de que necesito usar un método de plantilla (aunque estaba tan seguro de tener que usar un patrón de estrategia).

Tampoco puedo determinar quién sería responsable de crear el TurnTemplateobjeto, mientras que con el patrón de estrategia siento que tengo familias de objetos (las tres reglas) que podría crear fácilmente usando un patrón de fábrica abstracto. Entonces tendría una MancalaRuleFactory, WariRuleFactory, etc, y que crearían las instancias correctas de las reglas y la mano ME volver un RuleSetobjeto.

Digamos que uso el patrón de estrategia + fábrica abstracta y tengo un RuleSetobjeto que tiene algoritmos para las tres reglas. La única forma en que siento que todavía puedo usar el patrón de método de plantilla con esto es pasar este RuleSetobjeto a mi TurnTemplate. El "problema" que luego surge es que nunca necesitaría mis implementaciones concretas de TurnTemplate, estas clases se volverían obsoletas. En mis métodos protegidos en el TurnTemplatesolo podría llamar ruleSet.determineWinner(). Como consecuencia, la TurnTemplateclase ya no sería abstracta, sino que tendría que volverse concreta, ¿sigue siendo un patrón de método de plantilla?

Para resumir, ¿estoy pensando de la manera correcta o me estoy perdiendo algo fácil? Si estoy en el camino correcto, ¿cómo combino un patrón de estrategia y un patrón de método de plantilla?

Mekswoll
fuente
1
Diría que tu pensamiento es bastante acertado. Si los comentarios que recibe de su profesor no lo validan, pídale que señale los defectos o dé una pista de lo que busca en la respuesta. Deja de intentar leer las mentes. Asumir cosas sobre lo que tu profesor (y luego tus usuarios) quieren es posiblemente lo peor que puedes hacer.
Marjan Venema
" ... en el juego de Mancala y Wari, la forma en que se determina el ganador es exactamente la misma y el código se duplicaría " . ¿Por qué eso implica que el código está duplicado? Simplemente deje que ambas clases llamen a la misma función.
D Drmmr

Respuestas:

6

Después de mirar sus diseños, su primera y tercera iteraciones parecen ser diseños más elegantes. Sin embargo, mencionas que eres un estudiante y tu profesor te dio algunos comentarios. Sin saber exactamente cuál es su tarea o el propósito de la clase o más información sobre lo que sugirió su profesor, tomaría todo lo que digo a continuación con un grano de sal.

En su primer diseño, declara RuleInterfaceque es una interfaz que define cómo manejar el turno de cada jugador, cómo determinar si el juego ha terminado y cómo determinar un ganador después de que el juego termina. Parece que es una interfaz válida para una familia de juegos que experimenta variación. Sin embargo, dependiendo de los juegos, es posible que tenga un código duplicado. Estoy de acuerdo en que la flexibilidad para cambiar las reglas de un juego es algo bueno, pero también argumentaría que la duplicación de código es terrible por defectos. Si copia / pega código defectuoso entre implementaciones y uno tiene un error, ahora tiene varios errores que deben corregirse en diferentes ubicaciones. Si reescribe las implementaciones en diferentes momentos, podría introducir defectos en diferentes ubicaciones. Ninguno de esos es deseable.

Su segundo diseño parece bastante complejo, con un profundo árbol de herencia. Al menos, es más profundo de lo que esperaría para resolver este tipo de problema. También está comenzando a dividir los detalles de implementación en otras clases. En última instancia, estás modelando e implementando un juego. Este podría ser un enfoque interesante si fuera necesario mezclar y combinar sus reglas para determinar los resultados de un movimiento, el final del juego y un ganador, que no parece estar en los requisitos que ha mencionado . Sus juegos son conjuntos de reglas bien definidos, y trataría de encapsular los juegos tanto como pueda en entidades separadas.

Su tercer diseño es el que más me gusta. Mi única preocupación es que no está en el nivel correcto de abstracción. En este momento, parece que estás modelando un turno. Recomendaría considerar diseñar el juego. Considera que tienes jugadores que están haciendo movimientos en algún tipo de tablero, usando piedras. Tu juego requiere que estos actores estén presentes. A partir de ahí, su algoritmo no es doTurn()sino playGame(), que va del movimiento inicial al movimiento final, después de lo cual termina. Después del movimiento de cada jugador, ajusta el estado del juego, determina si el juego está en un estado final y, si lo está, determina el ganador.

Recomiendo echar un vistazo más de cerca a su primer y tercer diseño y trabajar con ellos. También podría ayudar pensar en términos de prototipos. ¿Cómo serían los clientes que usan estas interfaces? ¿Un enfoque de diseño tiene más sentido para implementar un cliente que realmente va a instanciar un juego y jugarlo? Debes darte cuenta de con qué está interactuando. En su caso particular, es la Gameclase y cualquier otro elemento asociado: no puede diseñar de forma aislada.


Como mencionas que eres un estudiante, me gustaría compartir algunas cosas de una época en que era TA para un curso de diseño de software:

  • Los patrones son simplemente una forma de capturar cosas que han funcionado en el pasado, pero abstrayéndolas a un punto donde pueden usarse en otros diseños. Cada catálogo de patrones de diseño le da un nombre a un patrón, explica sus intenciones y dónde se puede usar, y las situaciones en las que finalmente limitaría su diseño.
  • El diseño viene con experiencia. La mejor manera de ser bueno en el diseño no es simplemente enfocarse en los aspectos de modelado, sino darse cuenta de lo que implica la implementación de ese modelo. El diseño más elegante es útil si no se puede implementar fácilmente o si no se ajusta al diseño más grande del sistema o con otros sistemas.
  • Muy pocos diseños son "correctos" o "incorrectos". Mientras el diseño cumpla con los requisitos del sistema, no puede estar equivocado. Una vez que hay un mapeo de cada requisito en alguna representación de cómo el sistema va a cumplir ese requisito, el diseño no puede estar equivocado. En este punto, es solo una cuestión cualitativa sobre conceptos tales como flexibilidad o reutilización o comprobabilidad o mantenibilidad.
Thomas Owens
fuente
Gracias por el gran consejo, especialmente su punto sobre el hecho de que estaba en el nivel incorrecto de abstracción. Lo he cambiado ahora a uno GameTemplateque se siente mucho mejor. También me permite combinar un método de fábrica para inicializar jugadores, el tablero, etc.
Mekswoll
3

Tu confusión está justificada. La cuestión es que los patrones no son mutuamente excluyentes.

El método de plantilla es la base de muchos otros patrones, como Estrategia y Estado. Esencialmente, la interfaz de Estrategia contiene uno o más métodos de plantilla, cada uno de los cuales requiere que todos los objetos que implementan una estrategia tengan (al menos) algo así como un método doAction (). Esto permite que las estrategias se sustituyan entre sí.

En Java, una interfaz no es más que un conjunto de métodos de plantilla. Del mismo modo, cualquier método abstracto es esencialmente un método de plantilla. Este patrón (entre otros) era bien conocido por los diseñadores del lenguaje, por lo que lo incorporaron.

@ThomasOwens ofrece excelentes consejos para abordar su problema particular.

Matthew Flynn
fuente
0

Si te distraen los patrones de diseño, mi consejo es que primero hagas un prototipo del juego, luego los patrones deberían saltar sobre ti. No creo que sea realmente posible o aconsejable intentar diseñar un sistema perfectamente primero y luego implementarlo (de manera similar, me resulta desconcertante cuando las personas intentan escribir programas completos primero y luego compilar, en lugar de hacerlo poco a poco .) El problema es que es poco probable que pienses en cada escenario con el que tendrá que lidiar tu lógica, y durante la fase de implementación perderás toda esperanza o tratarás de mantenerte en tu diseño defectuoso original e introducir hacks, o incluso peor no entregar nada en absoluto.

James
fuente
2
Si bien en general estoy de acuerdo con usted , esta no es realmente una respuesta que resuelva el problema del OP. "No hagas eso" es una respuesta, pero bastante pobre si no proporciona una alternativa viable.
Yannis
@YannisRizos Y aunque también estoy de acuerdo contigo , sigo pensando que me dio buenos consejos (o una idea si el consejo no es la palabra correcta). Por esa razón, +1. Aunque, sí, técnicamente no es una respuesta muy útil.
compró777 el
-1

Vayamos a las tachuelas de latón. No hay absolutamente ninguna necesidad de ninguna interfaz de juego, sin patrones de diseño, sin clases abstractas y sin UML.

Si tiene una cantidad razonable de clases de soporte, como UI, simulación y lo que sea, entonces básicamente todo su código no específico de la lógica del juego se reutiliza de todos modos. Además, su usuario no cambia su juego dinámicamente. No cambias a 30Hz entre juegos. Juegas un juego durante aproximadamente media hora. Entonces su polimorfismo "dinámico" no es realmente dinámico en absoluto. Es bastante estático.

Entonces, la manera sensata de llegar aquí es usar una abstracción funcional genérica, como C # Actiono C ++ std::function, crear una clase Mancala, una Wari y una Kalah, y avanzar desde allí.

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

Hecho.

No llamas a juegos. Los juegos te llaman.

DeadMG
fuente
3
No veo cómo esto es útil en absoluto. La persona que hace esta pregunta es un estudiante. Está preguntando explícitamente sobre los principios de diseño y las interacciones entre dos patrones de diseño. Decir que no hay necesidad de patrones de diseño o UML podría ser cierto en algunos casos en la industria, pero pensar en modelos de diseño, patrones de diseño y UML podría ser el punto del ejercicio sobre el que se pregunta.
Thomas Owens
3
No hay nada que decir sobre ellos, porque no hay ningún lugar donde se apliquen. No tiene sentido gastar su tiempo pensando en cómo armaría completamente el diseño usando patrones de diseño, a menos que pretenda que sea una lección sobre cómo no usarlos.
DeadMG
44
Hay una tendencia preocupante / molesta en SoftDev donde los patrones de diseño utilizados se consideran más importantes que solo resolver el problema. Existe una sobre ingeniería.
James
2
@DeadMG: si bien ambos tienen razón, en el caso de OP el problema es aprobar el examen, y la solución a ese problema es usar patrones de diseño y UML. No importa lo acertados que estemos, si su profesor no está de acuerdo con sus soluciones, no aprobará.
Goran Jovic
2
Toda mi vida siempre he pensado que era "impuesto de latón" ... Wow, "tachuelas de latón" tiene mucho más sentido. lol
compró777