Hace una vez hice una pregunta en Stack Overflow sobre la herencia.
He dicho que diseño el motor de ajedrez en la moda OOP. Así que heredo todas mis piezas de la clase abstracta Pieza, pero la herencia aún continúa. Déjame mostrarte 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 {}
A los programadores se les ha encontrado un poco mi diseño sobre ingeniería y me sugirieron eliminar las clases de piezas de color y mantener el color de la pieza como miembro de la propiedad como a continuación.
public abstract class Piece
{
public Color Color { get; set; }
public abstract void MakeMove();
public abstract void TakeBackMove();
}
De esta manera, Piece puede conocer su color. Después de la implementación, he visto que mi implementación es la siguiente.
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
{
}
}
}
Ahora veo que mantienen la propiedad de color causando sentencias if en implementaciones. Me hace sentir que necesitamos piezas de color específicas en la jerarquía de herencia.
En tal caso, ¿tendría clases como WhitePawn, BlackPawn o iría a diseñar manteniendo la propiedad Color?
Sin ver tal problema, ¿cómo le gustaría comenzar a diseñar? ¿Mantener la propiedad Color o tener una solución de herencia?
Editar: Quiero especificar que mi ejemplo puede no coincidir completamente con el ejemplo de la vida real. Entonces, antes de tratar de adivinar los detalles de implementación, solo concentre la pregunta.
¿Realmente solo pregunto si usar la propiedad Color causará si las declaraciones son mejores para usar la herencia?
Respuestas:
Imagine que diseña una aplicación que contiene vehículos. ¿Creas algo como esto?
En la vida real, el color es una propiedad de un objeto (ya sea un automóvil o una pieza de ajedrez), y debe estar representado por una propiedad.
¿Son
MakeMove
yTakeBackMove
diferentes para negros y blancos? Para nada: las reglas son las mismas para ambos jugadores, lo que significa que esos dos métodos serán exactamente los mismos para negros y blancos , lo que significa que está agregando una condición donde no la necesita.Por otro lado, si usted tiene
WhitePawn
yBlackPawn
, yo no se sorprenda si pronto o más tarde que voy a escribir:o incluso algo como:
fuente
direction
propiedad y usarla para mover que usar la propiedad de color. Idealmente, el color solo debe usarse para renderizar, no para lógica.direction
propiedad es booleana. No veo qué hay de malo en eso. Lo que me confundió sobre la pregunta hasta el comentario anterior de Freshblood es por qué querría cambiar la lógica de movimiento en función del color. Diría que es más difícil de entender porque no tiene sentido que el color afecte el comportamiento. Si todo lo que quieres hacer es distinguir qué jugador posee qué pieza puedes poner en dos colecciones separadas y evitar la necesidad de una propiedad o herencia. Las piezas en sí mismas podrían ser felizmente inconscientes de a qué jugador pertenecen.direction
o tal vez unaplayer
propiedad en lugar de unacolor
en la lógica del juego.white castle's moves differ from black's
: ¿cómo? ¿Te refieres al enroque? Tanto el enroque del lado del rey como el del lado de la reina son 100% simétricos para blanco y negro.Lo que debe preguntarse es por qué realmente necesita esas declaraciones en su clase de Peón:
Estoy bastante seguro de que será suficiente tener un pequeño conjunto de funciones como
en su clase Piece e implemente todas las funciones en
Pawn
(y las otras derivaciones dePiece
) el uso de esas funciones, sin tener alguna de lasif
declaraciones anteriores. La única excepción puede ser la función para determinar la dirección de movimiento de un Peón por su color, ya que esto es específico para los peones, pero en casi todos los demás casos no necesitará ningún código de color específico.La otra pregunta interesante es si realmente debería tener diferentes subclases para diferentes tipos de piezas. Eso me parece razonable y natural, ya que las reglas para los movimientos de cada pieza son diferentes y seguramente puedes implementar esto usando diferentes funciones sobrecargadas para cada tipo de pieza.
Por supuesto, se podría tratar de modelar esto de una manera más genérica, mediante la separación de las propiedades de las piezas de sus reglas en movimiento, pero esto puede terminar en una clase abstracta
MovingRule
y una jerarquía subclaseMovingRulePawn
,MovingRuleQueen
, ... yo no iniciarlo que manera, ya que podría conducir fácilmente a otra forma de ingeniería excesiva.fuente
La herencia no es tan útil cuando la extensión no afecta las capacidades externas del objeto .
La propiedad Color no afecta cómo se usa un objeto Piece . Por ejemplo, todas las piezas podrían invocar un método moveForward o moveBackwards (incluso si el movimiento no es legal), pero la lógica encapsulada de la pieza utiliza la propiedad Color para determinar el valor absoluto que representa un movimiento hacia adelante o hacia atrás (-1 o + 1 en el "eje y"), en lo que respecta a su API, su superclase y subclases son objetos idénticos (ya que todos exponen los mismos métodos).
Personalmente, definiría algunas constantes que representan cada tipo de pieza, luego usaría una instrucción switch para ramificar en métodos privados que devuelvan posibles movimientos para "esta" instancia.
fuente
Board
clase (por ejemplo) podría encargarse de convertir estos conceptos en -1 o +1 dependiendo del color de la pieza.Personalmente no heredaría nada de nada
Piece
, porque no creo que ganes nada haciendo eso. Presumiblemente, a una pieza no le importa cómo se mueve, solo qué casillas son un destino válido para su próximo movimiento.Quizás podría crear una Calculadora de movimiento válida que conozca los patrones de movimiento disponibles para un tipo de pieza y pueda recuperar una lista de cuadrados de destino válidos, por ejemplo
fuente
Piece
". Parece que Piece es responsable de algo más que simplemente calcular movimientos, que es la razón para dividir el movimiento como una preocupación separada. Determinar qué pieza obtiene qué calculadora sería una cuestión de inyección de dependencia, por ejemplovar my_knight = new Piece(new KnightMoveCalculator())
En esta respuesta, supongo que por "motor de ajedrez", el autor de la pregunta significa "IA de ajedrez", no simplemente un motor de validación de movimiento.
Creo que usar OOP para piezas y sus movimientos válidos es una mala idea por dos razones:
Alejarme de las consideraciones de rendimiento, incluido el color de la pieza en la jerarquía de tipos, en mi opinión es una mala idea. Se encontrará en situaciones en las que necesita verificar si dos piezas tienen colores diferentes, por ejemplo, para capturar. La comprobación de esta condición se realiza fácilmente mediante una propiedad. Hacerlo usando
is WhitePiece
-tests sería más feo en comparación.También hay otra razón práctica para evitar mover la generación de movimientos a métodos específicos de pieza, a saber, que las diferentes piezas comparten movimientos comunes, por ejemplo, torres y reinas. Para evitar el código duplicado, tendría que factorizar el código común en un método base y tener otra llamada costosa.
Dicho esto, si es el primer motor de ajedrez que estás escribiendo, definitivamente recomendaría seguir principios de programación limpios y evitar los trucos de optimización descritos por el autor de Crafty. Tratar con los detalles de las reglas del ajedrez (enroque, promoción, pasatiempo) y las complejas técnicas de IA (tablas de hash, corte alfa-beta) ya es bastante desafiante.
fuente
WhichMovesArePossible
es un enfoque razonable.