Estou criando um jogo de tabuleiro (como xadrez) em Java, onde cada peça é seu próprio tipo (como Pawn
, Rook
etc.). Para a parte GUI do aplicativo, preciso de uma imagem para cada uma dessas peças. Desde fazendo pensa como
rook.image();
violar a separação da interface do usuário e da lógica de negócios, criarei um apresentador diferente para cada peça e, em seguida, mapeei os tipos de peça para seus apresentadores correspondentes, como
private HashMap<Class<Piece>, PiecePresenter> presenters = ...
public Image getImage(Piece piece) {
return presenters.get(piece.getClass()).image();
}
Por enquanto, tudo bem. No entanto, eu sinto que um guru prudente de OOP desaprovaria chamar um getClass()
método e sugeriria o uso de um visitante, por exemplo:
class Rook extends Piece {
@Override
public <T> T accept(PieceVisitor<T> visitor) {
return visitor.visitRook(this);
}
}
class ImageVisitor implements PieceVisitor<Image> {
@Override
public Image visitRook(Rook rook) {
return rookImage;
}
}
Eu gosto dessa solução (obrigado, guru), mas tem uma desvantagem significativa. Sempre que um novo tipo de peça é adicionado ao aplicativo, o PieceVisitor precisa ser atualizado com um novo método. Eu gostaria de usar meu sistema como uma estrutura de jogo de tabuleiro, onde novas peças poderiam ser adicionadas através de um processo simples, em que o usuário da estrutura forneceria apenas a implementação da peça e de seu apresentador, e simplesmente a conectaria à estrutura. Minha pergunta: existe uma solução limpa de POO sem instanceof
, getClass()
etc. , que permita esse tipo de extensibilidade?
fonte
Respostas:
Sim existe.
Deixe-me perguntar isso. Nos exemplos atuais, você encontra maneiras de mapear tipos de peças para imagens. Como isso resolve o problema de uma peça ser movida?
Uma técnica mais poderosa do que perguntar sobre o tipo é seguir Tell, não pergunte . E se cada peça tivesse uma
PiecePresenter
interface e tivesse a seguinte aparência:A construção ficaria assim:
O uso seria algo como:
A idéia aqui é evitar assumir a responsabilidade de fazer qualquer coisa pela qual outras coisas sejam responsáveis, não perguntando sobre ela nem tomando decisões com base nela. Em vez disso, mantenha uma referência a algo que sabe o que fazer sobre algo e diga-o para fazer algo sobre o que você sabe.
Isso permite polimorfismo. Você não se importa com o que está falando. Você não se importa com o que tem a dizer. Você apenas se importa que ele possa fazer o que você precisa.
Um bom diagrama que os mantém em camadas separadas, segue o dizer não pergunte e mostra como não acoplar injustamente camada a camada é o seguinte :
Ele adiciona uma camada de caso de uso que não usamos aqui (e certamente podemos adicionar), mas estamos seguindo o mesmo padrão que você vê no canto inferior direito.
Você também notará que o Presenter não usa herança. Ele usa composição. A herança deve ser uma maneira de último recurso para obter polimorfismo. Eu prefiro designs que favorecem o uso de composição e delegação. É um pouco mais de digitação do teclado, mas é muito mais potente.
fonte
Que tal:
Seu modelo (as classes de figuras) também possui métodos comuns que podem ser necessários em outro contexto:
As imagens a serem usadas para exibir uma determinada figura obtêm nomes de arquivos por um esquema de nomenclatura:
Em seguida, você pode carregar a imagem apropriada sem acessar informações sobre as classes java.
Eu acho que você não deveria se concentrar tanto nas aulas . Em vez disso, pense em termos de objetos de negócios .
E a solução genérica é um mapeamento de qualquer tipo. IMHO, o truque é mover esse mapeamento do código para um recurso mais fácil de manter.
Meu exemplo faz esse mapeamento por convenção, que é bastante fácil de implementar e evita adicionar informações relacionadas à exibição no modelo de negócios . Por outro lado, você pode considerá-lo um mapeamento "oculto" porque não é expresso em lugar algum.
Outra opção é ver isso como um caso de negócios separado com suas próprias camadas MVC, incluindo uma camada de persistência que contém o mapeamento.
fonte
Eu criaria uma classe UI / view separada para cada peça que contenha as informações visuais. Cada uma dessas classes de visualização por partes tem um ponteiro para o modelo / empresa correspondente, que contém a posição e as regras do jogo da peça.
Então, pegue um peão, por exemplo:
Isso permite a separação completa da lógica e da interface do usuário. Você pode passar o ponteiro da peça lógica para uma classe de jogo que lidaria com a movimentação das peças. A única desvantagem é que a instanciação teria que acontecer em uma classe de interface do usuário.
fonte
Piece* p
. Como sei que preciso criar umPawnView
para exibi-lo, e não umRookView
ouKingView
? Ou preciso criar uma visualização ou apresentador imediatamente sempre que criar uma nova peça? Basicamente, essa seria a solução do @ CandiedOrange com as dependências invertidas. Nesse caso, oPawnView
construtor também pode usar aPawn*
, não apenas aPiece*
.Eu abordaria isso tornando
Piece
genérico, onde seu parâmetro é o tipo de uma enumeração que identifica o tipo de peça, cada peça tendo uma referência a um desses tipos. Em seguida, a interface do usuário poderia usar um mapa da enumeração como antes:Isso tem duas vantagens interessantes:
Primeiro, aplicável à maioria das linguagens de tipo estaticamente: se você parametrizar sua placa com o tipo de peça a ser visualizada, não poderá inserir o tipo errado de peça nela.
Segundo, e talvez o mais interessante, se você estiver trabalhando em Java (ou outras linguagens JVM), observe que cada valor de enum não é apenas um objeto independente, mas também pode ter sua própria classe. Isso significa que você pode usar seus objetos de tipo de peça como objetos de estratégia para informar o comportamento da peça:
(Obviamente, as implementações reais precisam ser mais complexas que isso, mas espero que você entenda)
fonte
Sou programador pragmático e realmente não me importo com o que é arquitetura limpa ou suja. Acredito que os requisitos e devem ser tratados de maneira simples.
Sua exigência é que a lógica do seu aplicativo de xadrez seja representada em diferentes camadas de apresentação (dispositivos), como na Web, aplicativo móvel ou mesmo aplicativo de console, portanto, você precisa dar suporte a esses requisitos. Você pode preferir usar cores muito diferentes, imagens de peças em cada dispositivo.
Como você viu, o parâmetro do apresentador deve ser passado em cada dispositivo (camada de apresentação) de maneira diferente. Isso significa que sua camada de apresentação decidirá como representar cada peça. O que há de errado nesta solução?
fonte
Há outra solução que o ajudará a abstrair completamente a lógica da interface do usuário e do domínio. Seu painel deve ser exposto à sua camada de interface do usuário e sua camada de interface do usuário pode decidir como representar peças e posições.
Para conseguir isso, você pode usar a sequência Fen . Fen string é basicamente uma informação do estado do tabuleiro e fornece as peças atuais e suas posições a bordo. Para que seu quadro possa ter um método que retorne o estado atual do quadro via seqüência de caracteres Fen, sua camada de interface do usuário poderá representar o quadro como desejar. É assim que os atuais mecanismos de xadrez funcionam. Os mecanismos de xadrez são aplicativos de console sem GUI, mas os usamos por meio de uma GUI externa. O mecanismo de xadrez se comunica com a GUI por meio de seqüências de caracteres fen e notação de xadrez.
Você está perguntando isso e se eu adicionar uma nova peça? Não é realista que o xadrez introduza uma nova peça. Isso seria uma grande mudança no seu domínio. Então, siga o princípio YAGNI.
fonte