Era uma vez, fiz uma pergunta no Stack Overflow sobre herança.
Eu disse que projeto um mecanismo de xadrez da maneira OOP. Então, eu herdo todas as minhas peças da classe abstrata Piece, mas a herança ainda continua. Deixe-me mostrar por código
public abstract class Piece
{
public void MakeMove();
public void TakeBackMove();
}
public abstract class Pawn: Piece {}
public class WhitePawn :Pawn {}
public class BlackPawn:Pawn {}
Programadores consideraram meu projeto um pouco mais do que engenharia e sugeriram remover classes de peças coloridas e manter a cor das peças como membro da propriedade, como abaixo.
public abstract class Piece
{
public Color Color { get; set; }
public abstract void MakeMove();
public abstract void TakeBackMove();
}
Dessa maneira, Piece pode conhecer sua cor. Após a implementação, vi a minha implementação como abaixo.
public abstract class Pawn: Piece
{
public override void MakeMove()
{
if (this.Color == Color.White)
{
}
else
{
}
}
public override void TakeBackMove()
{
if (this.Color == Color.White)
{
}
else
{
}
}
}
Agora vejo que manter a propriedade color causando declarações if nas implementações. Isso me faz sentir que precisamos de peças de cores específicas na hierarquia de herança.
Nesse caso, você teria aulas como WhitePawn, BlackPawn ou iria projetar mantendo a propriedade Color?
Sem esse problema, como você gostaria de iniciar o design? Manter a propriedade Color ou ter uma solução de herança?
Editar: quero especificar que meu exemplo pode não corresponder completamente ao exemplo da vida real. Portanto, antes de tentar adivinhar os detalhes da implementação, concentre a questão.
Na verdade, eu simplesmente pergunto se o uso da propriedade Color causará se as instruções usarem melhor a herança?
Respostas:
Imagine que você projeta um aplicativo que contém veículos. Você cria algo assim?
Na vida real, a cor é uma propriedade de um objeto (um carro ou uma peça de xadrez) e deve ser representada por uma propriedade.
São
MakeMove
eTakeBackMove
diferentes para negros e brancos? Nem um pouco: as regras são as mesmas para os dois jogadores, o que significa que esses dois métodos serão exatamente os mesmos para pretos e brancos , o que significa que você está adicionando uma condição em que não precisa.Por outro lado, se você tiver
WhitePawn
eBlackPawn
, não ficarei surpreso se, cedo ou tarde, você escrever:ou até algo como:
fonte
direction
propriedade e usá-la para mover do que usar a propriedade color. Idealmente, a cor deve ser usada apenas para renderização, não para lógica.direction
propriedade é um booleano. Não vejo o que há de errado nisso. O que me confundiu sobre a questão até o comentário de Freshblood acima é por que ele gostaria de mudar a lógica do movimento com base na cor. Eu diria que isso é mais difícil de entender porque não faz sentido a cor afetar o comportamento. Se tudo o que você quer fazer é distinguir qual jogador possui qual peça, você pode colocá-las em duas coleções separadas e evitar a necessidade de uma propriedade ou herança. As peças em si podem ser alegremente inconscientes de qual jogador elas pertencem.direction
ou talvez umaplayer
propriedade em vez de umacolor
na lógica do jogo.white castle's moves differ from black's
- como? Você quer dizer roque? O castelo do lado do rei e o castelo do lado da rainha são 100% simétricos para preto e branco.O que você deve se perguntar é por que você realmente precisa dessas declarações na sua classe de peão:
Tenho certeza de que será suficiente ter um pequeno conjunto de funções como
na sua classe Piece e implemente todas as funções
Pawn
(e as demais derivações dePiece
) usando essas funções, sem nunca ter nenhuma dessasif
instruções acima. A única exceção pode ser a função de determinar a direção do movimento de um peão por sua cor, já que isso é específico para peões, mas em quase todos os outros casos você não precisará de nenhum código específico de cor.A outra pergunta interessante é se você realmente deve ter subclasses diferentes para diferentes tipos de peças. Isso me parece razoável e natural, já que as regras para os movimentos de cada peça são diferentes e você certamente pode implementá-lo usando diferentes funções sobrecarregadas para cada tipo de peça.
Claro, pode-se tentar modelar isso de uma forma mais genérica, separando as propriedades dos pedaços de suas regras em movimento, mas isso pode acabar em uma classe abstrata
MovingRule
e uma hierarquia de subclasseMovingRulePawn
,MovingRuleQueen
... eu não iria iniciar-se que maneira, uma vez que poderia facilmente levar a outra forma de excesso de engenharia.fonte
A herança não é tão útil quando a extensão não afeta os recursos externos do objeto .
A propriedade Color não afeta como um objeto Piece é usado . Por exemplo, todas as peças podem chamar um método moveForward ou moveBackwards (mesmo se a movimentação não for legal), mas a lógica encapsulada da Piece usa a propriedade Color para determinar o valor absoluto que representa um movimento para frente ou para trás (-1 ou + 1 no "eixo y"), no que diz respeito à sua API, sua superclasse e subclasse são objetos idênticos (pois todos expõem os mesmos métodos).
Pessoalmente, eu definiria algumas constantes que representam cada tipo de peça e, em seguida, usaria uma instrução switch para ramificar em métodos privados que retornam possíveis movimentos para "this" instância.
fonte
Board
turma (por exemplo) pode se encarregar de converter esses conceitos em -1 ou +1, dependendo da cor da peça.Pessoalmente, eu não herdaria nada
Piece
, porque acho que você não ganha nada com isso. Presumivelmente, uma peça não se importa com a maneira como ela se move, apenas quais quadrados são um destino válido para a sua próxima jogada.Talvez você possa criar uma Calculadora de Movimentos Válidos que conheça os padrões de movimento disponíveis para um tipo de peça e seja capaz de recuperar uma lista de quadrados de destino válidos, por exemplo
fonte
Piece
". Parece que Piece é responsável por mais do que simplesmente calcular movimentos, que é a razão para dividir o movimento como uma preocupação separada. Determinar qual peça fica com qual calculadora seria uma questão de injeção de dependência, por exemplovar my_knight = new Piece(new KnightMoveCalculator())
Nesta resposta, estou assumindo que, por "mecanismo de xadrez", o autor da pergunta significa "IA de xadrez", não apenas um mecanismo de validação de movimento.
Eu acho que usar OOP para peças e seus movimentos válidos é uma má idéia por dois motivos:
Afastar-me das considerações de desempenho, incluindo a cor da peça na hierarquia de tipos, é na minha opinião uma má ideia. Você se encontrará em situações em que precisa verificar se duas peças têm cores diferentes, por exemplo, para capturar. A verificação dessa condição é feita facilmente usando uma propriedade Fazer isso usando
is WhitePiece
-tests seria mais feio em comparação.Há também outra razão prática para evitar a geração de movimentos de movimentação para métodos específicos de peças, a saber, que peças diferentes compartilham movimentos comuns, por exemplo, torres e rainhas. Para evitar código duplicado, você teria que fatorar o código comum em um método base e ter mais uma chamada dispendiosa.
Dito isto, se é o primeiro mecanismo de xadrez que você está escrevendo, eu recomendaria definitivamente seguir princípios de programação limpos e evitar os truques de otimização descritos pelo autor de Crafty. Lidar com as especificidades das regras do xadrez (rolagem, promoção, passante) e técnicas complexas de IA (hashing boards, corte alfa-beta) já é bastante desafiador.
fonte
WhichMovesArePossible
é uma abordagem razoável.