Combinando método de modelo com estratégia

14

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: insira a descrição da imagem aqui

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: insira a descrição da imagem aqui

Cada jogo (Mancala, Wari, Kalah, ...) teria apenas atributo do tipo de interface de cada regra, ou seja, WinnerDeterminere 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: insira a descrição da imagem aqui

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 TurnTemplateobjeto, 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 RuleSetobjeto.

Digamos que eu use o padrão de fábrica abstrato estratégia e eu tenho um RuleSetobjeto 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 RuleSetobjeto 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 TurnTemplateeu poderia simplesmente chamar ruleSet.determineWinner(). Como conseqüência, a TurnTemplateclasse 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?

Mekswoll
fonte
1
Eu diria que seu pensamento está bem no lugar. Se o feedback que você obtiver do seu professor não validar isso, peça que ele aponte as falhas ou dê uma dica do que ele está procurando na resposta. Pare de tentar ler mentes. Presumir que o que seu professor (e mais tarde seus usuários) deseja é possivelmente o pior que você pode fazer.
Marjan Venema
" ... no jogo de Mancala e Wari, a maneira como o vencedor é determinado é exatamente o mesmo e o código seria duplicado. " Por que isso implica que o código é duplicado? Apenas deixe as duas classes chamarem a mesma função.
D Drmmr

Respostas:

6

Depois de analisar seus designs, a primeira e a terceira iterações parecem ter designs mais elegantes. No entanto, você menciona que é estudante e seu professor lhe deu algum feedback. Sem saber exatamente qual é a sua tarefa ou o objetivo da classe ou mais informações sobre o que seu professor sugeriu, eu aceitaria qualquer coisa que eu disser abaixo com um pouco de sal.

No seu primeiro design, você declara RuleInterfaceser uma interface que define como lidar com a vez de cada jogador, como determinar se o jogo terminou e como determinar um vencedor após o término do jogo. Parece que essa é uma interface válida para uma família de jogos que experimenta variações. No entanto, dependendo dos jogos, você pode ter um código duplicado. Concordo que a flexibilidade de alterar as regras de um jogo é uma coisa boa, mas também argumentaria que a duplicação de código é terrível para defeitos. Se você copiar / colar código defeituoso entre as implementações e houver um erro, agora você terá vários erros que precisam ser corrigidos em locais diferentes. Se você reescrever as implementações em momentos diferentes, poderá introduzir defeitos em locais diferentes. Nenhuma delas é desejável.

Seu segundo design parece bastante complexo, com uma árvore de herança profunda. Pelo menos, é mais profundo do que eu esperaria para resolver esse tipo de problema. Você também está começando a dividir os detalhes da implementação em outras classes. Por fim, você está modelando e implementando um jogo. Essa pode ser uma abordagem interessante se você precisar combinar e combinar suas regras para determinar os resultados de uma jogada, o final do jogo e um vencedor, que não parecem estar nos requisitos mencionados. . Seus jogos são conjuntos de regras bem definidos e eu tentaria encapsular os jogos o máximo possível em entidades separadas.

Seu terceiro design é aquele que eu mais gosto. Minha única preocupação é que não esteja no nível certo de abstração. No momento, você parece estar modelando uma curva. Eu recomendaria considerar projetar o jogo. Considere que você tem jogadores que estão fazendo movimentos em algum tipo de tabuleiro, usando pedras. Seu jogo exige que esses atores estejam presentes. A partir daí, seu algoritmo não é doTurn()mais playGame(), que vai do movimento inicial para o movimento final, após o qual termina. Após a jogada de cada jogador, ele ajusta o estado do jogo, determina se o jogo está em um estado final e, se estiver, determina o vencedor.

Eu recomendaria examinar mais de perto seus primeiro e terceiro projetos e trabalhar com eles. Também pode ajudar a pensar em termos de protótipos. Como seriam os clientes que usam essas interfaces? Uma abordagem de design faz mais sentido para implementar um cliente que realmente instancia um jogo e joga? Você precisa perceber com o que ele está interagindo. No seu caso particular, é a Gameclasse e quaisquer outros elementos associados - você não pode projetar isoladamente.


Como você menciona que é estudante, gostaria de compartilhar algumas coisas de uma época em que eu era o TA de um curso de design de software:

  • Os padrões são simplesmente uma maneira de capturar coisas que funcionaram no passado, mas abstraindo-as a um ponto em que podem ser usadas em outros designs. Cada catálogo de padrões de design atribui um nome a um padrão, explica suas intenções e onde ele pode ser usado, além de situações em que isso acabaria restringindo seu design.
  • O design vem com experiência. A melhor maneira de se aperfeiçoar no design não é simplesmente se concentrar nos aspectos da modelagem, mas perceber o que é necessário para a implementação desse modelo. O design mais elegante é útil se não puder ser implementado com facilidade ou não se encaixar no design maior do sistema ou com outros sistemas.
  • Muito poucos designs são "certos" ou "errados". Desde que o design atenda aos requisitos do sistema, ele não pode estar errado. Depois que há um mapeamento de cada requisito para alguma representação de como o sistema atenderá a esse requisito, o design não pode estar errado. Neste momento, é apenas qualitativo sobre conceitos como flexibilidade ou reutilização ou capacidade de teste ou manutenção.
Thomas Owens
fonte
Obrigado pelo ótimo conselho, especialmente pelo seu ponto de vista sobre o fato de estar no nível errado de abstração. Eu mudei agora para um GameTemplateque parece muito melhor. Também me permite combinar um método de fábrica para inicializar jogadores, o tabuleiro etc.
Mekswoll 28/10
3

Sua confusão é justificada. O fato é que os padrões não são mutuamente exclusivos.

O método de modelo é a base para vários outros padrões, como Estratégia e Estado. Essencialmente, a interface Strategy contém um ou mais métodos de modelo, cada um exigindo que todos os objetos que implementam uma estratégia tenham (pelo menos) algo como um método doAction (). Isso permite que as estratégias sejam substituídas uma pela outra.

Em Java, uma interface nada mais é que um conjunto de métodos de modelo. Da mesma forma, qualquer método abstrato é essencialmente um método de modelo. Esse padrão (entre outros) era bem conhecido pelos designers da linguagem, então eles o incorporaram.

A @ThomasOwens oferece excelentes conselhos para abordar seu problema específico.

Matthew Flynn
fonte
0

Se você está sendo distraído pelos padrões de design, meu conselho é que você protótipo do jogo primeiro, então os padrões devem aparecer em você. Eu não acho que seja realmente possível ou aconselhável tentar projetar um sistema perfeitamente primeiro e depois implementá-lo (de maneira semelhante, acho perplexo quando as pessoas tentam escrever programas inteiros primeiro e depois compilar, em vez de fazê-lo pouco a pouco .) O problema é que é improvável que você pense em todos os cenários com os quais sua lógica terá que lidar, e durante a fase de implementação você perderá toda a esperança ou tentará manter seu design defeituoso original e introduzir hacks, ou até pior não entregar nada.

James
fonte
2
Embora eu concorde com você em geral , essa não é realmente uma resposta que resolva o problema do OP. "Não faça isso" é uma resposta, mas um pouco ruim se não fornecer uma alternativa viável.
yannis
@YannisRizos E, embora eu também concorde com você , ainda acho que ele deu bons conselhos (ou insights, se conselho não for a palavra certa). Por esse motivo, +1. Mesmo assim, sim, não é tecnicamente uma resposta muito útil.
bought777
-1

Vamos ao que interessa. Não há absolutamente nenhuma necessidade de interface de jogo, nenhum padrão de design, nenhuma classe abstrata e nenhuma UML.

Se você tiver uma quantidade razoável de classes de suporte, como interface do usuário, simulação e qualquer outra coisa, basicamente todo o seu código específico da lógica do jogo será reutilizado de qualquer maneira. Além disso, seu usuário não muda seu jogo dinamicamente. Você não gira a 30Hz entre os jogos. Você joga um jogo por cerca de meia hora. Portanto, seu polimorfismo "dinâmico" não é realmente dinâmico. É bastante estático.

Portanto, a melhor maneira de ir aqui é usar uma abstração funcional genérica, como C # Actionou C ++ std::function, criar uma classe Mancala, Wari e Kalah, e partir daí.

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

Feito.

Você não liga para jogos. Os jogos ligam para você.

DeadMG
fonte
3
Não vejo como isso seja útil. A pessoa que faz esta pergunta é um estudante. Ele está perguntando explicitamente sobre os princípios de design e as interações entre dois padrões de design. Dizer que não há necessidade de padrões de design ou UML pode ser verdade em alguns casos no setor, mas pensar em modelos de design, padrões de design e UML pode ser o ponto do exercício sobre o qual está sendo perguntado.
Thomas Owens
3
Não há nada a dizer sobre eles, porque não há lugar onde eles se apliquem. Não faz sentido gastar seu tempo pensando em como você projetaria completamente o design usando padrões de design, a menos que você pretenda que seja uma lição sobre como não usá-los.
27512 DeadMG
4
Há uma tendência preocupante / irritante no SoftDev, em que os padrões de design usados ​​são considerados mais importantes do que apenas resolver o problema. Existe o excesso de engenharia.
James
2
@DeadMG: enquanto vocês dois estão essencialmente certos, no caso do OP, o problema está passando no exame e a solução para esse problema é usar padrões de design e UML. Não importa o quanto estamos certos, se o professor não concordar com suas soluções, ele não passará.
precisa
2
Toda a minha vida eu sempre pensei que era "imposto sobre metais" ... Uau, "tachinhas" faz muito mais sentido. lol
bought777