Uma tarefa na minha aula de engenharia de software é projetar um aplicativo que possa desempenhar diferentes formas em um jogo específico. O jogo em questão é Mancala, alguns desses jogos são chamados Wari ou Kalah. Esses jogos diferem em alguns aspectos, mas, para minha pergunta, é importante saber que os jogos podem diferir no seguinte:
- A maneira pela qual o resultado de uma movimentação é tratado
- A maneira pela qual o final do jogo é determinado
- A maneira pela qual o vencedor é determinado
A primeira coisa que me veio à cabeça para projetar isso foi usar o padrão de estratégia; tenho uma variação nos algoritmos (as regras reais do jogo). O design pode ficar assim:
Então pensei comigo mesmo que, no jogo de Mancala e Wari, a maneira como o vencedor é determinado é exatamente o mesmo e o código seria duplicado. Eu não acho que isso seja, por definição, uma violação da 'regra única, um lugar' ou do princípio DRY, visto que uma mudança nas regras de Mancala não significaria automaticamente que essa regra também deveria ser alterada em Wari. No entanto, pelo feedback que recebi do meu professor, tive a impressão de encontrar um design diferente.
Eu então vim com isso:
Cada jogo (Mancala, Wari, Kalah, ...) teria apenas atributo do tipo de interface de cada regra, ou seja, WinnerDeterminer
e se houver uma versão do Mancala 2.0 que seja igual ao Mancala 1.0, exceto pela forma como o vencedor for determinado, ele poderá use as versões do Mancala.
Eu acho que a implementação dessas regras como padrão de estratégia é certamente válida. Mas o verdadeiro problema surge quando quero projetá-lo ainda mais.
Ao ler sobre o padrão do método de modelo, pensei imediatamente que poderia ser aplicado a esse problema. As ações que são executadas quando um usuário faz uma mudança são sempre as mesmas e na mesma ordem, a saber:
- depositar pedras nos buracos (isso é o mesmo para todos os jogos, então seria implementado no próprio método de modelo)
- determinar o resultado da mudança
- determinar se o jogo terminou por causa da jogada anterior
- se o jogo terminar, determine quem ganhou
Esses três últimos passos estão todos no meu padrão de estratégia descrito acima. Estou tendo muitos problemas para combinar esses dois. Uma solução possível que encontrei seria abandonar o padrão de estratégia e fazer o seguinte:
Realmente não vejo a diferença de design entre o padrão de estratégia e isso? Mas tenho certeza de que preciso usar um método de modelo (apesar de ter a mesma certeza de ter que usar um padrão de estratégia).
Também não posso determinar quem seria responsável pela criação do TurnTemplate
objeto, enquanto que com o padrão de estratégia sinto que tenho famílias de objetos (as três regras) que eu poderia criar facilmente usando um padrão de fábrica abstrato. Eu teria, então, um MancalaRuleFactory
, WariRuleFactory
, etc. e que iria criar as instâncias corretas das regras e mão-me de volta um RuleSet
objeto.
Digamos que eu use o padrão de fábrica abstrato estratégia e eu tenho um RuleSet
objeto que possui algoritmos para as três regras nele. A única maneira que sinto que ainda posso usar o padrão do método template é passar esse RuleSet
objeto para o meu TurnTemplate
. O 'problema' que então surge é que eu nunca precisaria de minhas implementações concretas do TurnTemplate
, essas classes se tornariam obsoletas. Nos meus métodos protegidos no TurnTemplate
eu poderia simplesmente chamar ruleSet.determineWinner()
. Como conseqüência, a TurnTemplate
classe não seria mais abstrata, mas precisaria se tornar concreta. Ainda é um padrão de método de modelo?
Para resumir, estou pensando da maneira certa ou estou perdendo algo fácil? Se estou no caminho certo, como posso combinar um padrão de estratégia e um padrão de método de modelo?