Antecedentes
Aquí está el problema real en el que estoy trabajando: quiero una forma de representar cartas en el juego de cartas Magic: The Gathering . La mayoría de las cartas en el juego son cartas de aspecto normal, pero algunas de ellas están divididas en dos partes, cada una con su propio nombre. Cada mitad de estas tarjetas de dos partes se trata como una tarjeta en sí. Entonces, para mayor claridad, usaré Cardsolo para referirme a algo que sea una tarjeta regular o la mitad de una tarjeta de dos partes (en otras palabras, algo con un solo nombre).

Entonces tenemos un tipo base, Card. El propósito de estos objetos es realmente mantener las propiedades de la tarjeta. Realmente no hacen nada por sí mismos.
interface Card {
String name();
String text();
// etc
}
Hay dos subclases de Card, que estoy llamando PartialCard(la mitad de una tarjeta de dos partes) y WholeCard(una tarjeta normal). PartialCardtiene dos métodos adicionales: PartialCard otherPart()y boolean isFirstPart().
Representantes
Si tengo un mazo, debería estar compuesto de WholeCards, no de Cards, como a Cardpodría ser un PartialCard, y eso no tendría sentido. Entonces, quiero un objeto que represente una "tarjeta física", es decir, algo que pueda representar uno WholeCardo dos PartialCards. Estoy tentativamente llamando a este tipo Representative, y Cardtendría el método getRepresentative(). A Representativecasi no proporcionaría información directa sobre las tarjetas que representa, solo las señalaría. Ahora, mi idea brillante / loca / tonta (usted decide) es que WholeCard hereda de ambos Card y Representative. Después de todo, ¡son cartas que se representan a sí mismas! WholeCards podría implementarse getRepresentativecomo return this;.
En cuanto a PartialCards, no se representan a sí mismos, pero tienen un externo Representativeque no es un Card, pero proporciona métodos para acceder a los dos PartialCards.
Creo que esta jerarquía de tipos tiene sentido, pero es complicada. Si pensamos en Cards como "tarjetas conceptuales" Representativeys como "tarjetas físicas", bueno, ¡la mayoría de las tarjetas son ambas! Creo que podría argumentar que las tarjetas físicas de hecho contienen tarjetas conceptuales y que no son lo mismo , pero diría que lo son.
Necesidad de fundición de tipo
Debido a que PartialCardsy WholeCardsson ambos Card, y generalmente no hay una buena razón para separarlos, normalmente solo estaría trabajando con ellos Collection<Card>. Así que a veces necesito lanzar PartialCards para acceder a sus métodos adicionales. En este momento, estoy usando el sistema descrito aquí porque realmente no me gustan los moldes explícitos. Y Card, como , Representativenecesitaría ser lanzado a a WholeCardo Composite, para acceder a los Cards reales que representan.
Así que solo para resumen:
- Tipo base
Representative - Tipo base
Card - Tipo
WholeCard extends Card, Representative(no se necesita acceso, se representa a sí mismo) - Tipo
PartialCard extends Card(da acceso a otra parte) - Tipo
Composite extends Representative(da acceso a ambas partes)
¿Esto es una locura? Creo que en realidad tiene mucho sentido, pero honestamente no estoy seguro.
fuente

Respuestas:
Me parece que deberías tener una clase como
El código que se ocupa de la tarjeta física puede manejar la clase de tarjeta física, y el código que se ocupa de la tarjeta lógica puede manejar eso.
No importa si crees que la tarjeta física y la lógica son lo mismo. No asuma que solo porque son el mismo objeto físico, deberían ser el mismo objeto en su código. Lo que importa es si adoptar ese modelo hace que el codificador sea más fácil de leer y escribir. El hecho es que la adopción de un modelo más simple donde cada tarjeta física se trata como una colección de tarjeta lógica de manera consistente, el 100% del tiempo, dará como resultado un código más simple.
fuente
Para ser franco, creo que la solución propuesta es demasiado restrictiva y demasiado retorcida y desarticulada de la realidad física son los modelos, con poca ventaja.
Sugeriría una de dos alternativas:
Opción 1. Trátelo como una sola tarjeta, identificada como Half A // Half B , como el sitio MTG enumera Wear // Tear . Pero, permita que su
Cardentidad contenga N de cada atributo: nombre jugable, costo de maná, tipo, rareza, texto, efectos, etc.Opción 2. No todo lo que es diferente de la Opción 1, modelarlo según la realidad física. Tienes una
Cardentidad que representa una tarjeta física . Y, su propósito es entonces mantener NPlayablecosas. EsosPlayablepueden tener un nombre distinto, costo de maná, lista de efectos, lista de habilidades, etc. Y su "físico"Cardpuede tener su propio identificador (o nombre) que es un compuesto delPlayablenombre de cada uno , al igual que la base de datos MTG parece hacer.Creo que cualquiera de estas opciones está bastante cerca de la realidad física. Y, creo que será beneficioso para cualquiera que vea su código. (Como tú mismo en 6 meses).
fuente
Esta oración es una señal de que hay algo mal en su diseño: en OOP, cada clase debe tener exactamente un rol, y la falta de comportamiento revela una posible clase de datos , que es un mal olor en el código.
En mi humilde opinión, suena un poco extraño, e incluso un poco extraño. Un objeto de tipo "Tarjeta" debe representar una tarjeta. Período.
No sé nada sobre Magic: la reunión , pero supongo que quieres usar tus cartas de una manera similar, sea cual sea su estructura real: quieres mostrar una representación de cadena, quieres calcular un valor de ataque, etc.
Para el problema que describe, recomendaría un patrón de diseño de composición , a pesar de que este DP generalmente se presenta para resolver un problema más general:
Cardinterfaz, como ya lo hizo.ConcreteCard, que implementeCardy defina una tarjeta de cara simple. No dudes en poner el comportamiento de una carta normal en esta clase.CompositeCard, que implementeCardy tenga dos s adicionales (y a priori privados)Card. Vamos a llamarlosleftCardyrightCard.La elegancia del enfoque es que a
CompositeCardcontiene dos tarjetas, que pueden ser ConcreteCard o CompositeCard. En su juego,leftCardyrightCardprobablemente será sistemáticamenteConcreteCards, pero el Patrón de diseño le permite diseñar composiciones de mayor nivel de forma gratuita si lo desea. La manipulación de su tarjeta no tendrá en cuenta el tipo real de sus tarjetas y, por lo tanto, no necesita cosas como lanzar a la subclase.CompositeCarddebe implementar los métodos especificados enCard, por supuesto, y lo hará teniendo en cuenta el hecho de que dicha tarjeta está compuesta por 2 tarjetas (más, si lo desea, algo específico de laCompositeCardtarjeta en sí. Por ejemplo, puede que desee siguiente implementación:Al hacer eso, puede usar
CompositeCardexactamente como lo hace para cualquieraCard, y el comportamiento específico se oculta gracias al polimorfismo.Si está seguro de que un
CompositeCardsiempre contendrá dosCards normales , puede mantener la idea y simplemente usarlaConcreateCardcomo un tipo paraleftCardyrightCard.fuente
CardenCompositeCardque esté implementando el patrón decorador . También recomiendo al OP que use esta solución, ¡el decorador es el camino a seguir!CompositeCardque no exponga métodos adicionales,CompositeCardes solo un decorador.Tal vez todo es una Carta cuando está en la baraja o en el cementerio, y cuando la juegas, construyes una Criatura, Tierra, Encantamiento, etc. a partir de uno o más objetos de Carta, todos los cuales implementan o extienden Jugable. Luego, un compuesto se convierte en un Jugable único cuyo constructor toma dos Cartas parciales, y una carta con un pateador se convierte en un Jugable cuyo constructor toma un argumento de maná. El tipo refleja lo que puede hacer con él (dibujar, bloquear, disipar, tocar) y lo que puede afectarlo. O un Jugable es solo una Carta que tiene que ser revertida cuidadosamente (perdiendo cualquier bonificación y contadores, dividiéndose) cuando se retira del juego, si es realmente útil usar la misma interfaz para invocar una carta y predecir lo que hace.
Quizás Card y Jugable tengan un efecto.
fuente
El patrón de visitante es una técnica clásica para recuperar información de tipo oculto. Podemos usarlo (una ligera variación aquí) aquí para discernir entre los dos tipos incluso cuando están almacenados en variables de abstracción superior.
Comencemos con esa abstracción superior, una
Cardinterfaz:Puede haber un poco más de comportamiento en la
Cardinterfaz, pero la mayoría de los captadores de propiedades se mueven a una nueva claseCardProperties:Ahora podemos tener una
SimpleCardrepresentación de una tarjeta completa con un solo conjunto de propiedades:Vemos cómo el
CardPropertiesy lo que está por escribirCardVisitorestán empezando a encajar. Hagamos unaCompoundCardpara representar una tarjeta con dos caras:Los
CardVisitorcomienzos a surgir. Intentemos escribir esa interfaz ahora:(Esta es una primera versión de la interfaz por ahora. Podemos hacer mejoras, que se discutirán más adelante).
Ahora hemos desarrollado todas las partes. Ahora solo necesitamos unirlos:
El tiempo de ejecución manejará el envío a la versión correcta del
#visitmétodo a través del polimorfismo en lugar de tratar de romperlo.En lugar de utilizar una clase anónima, incluso puede promocionarla
CardVisitora una clase interna o incluso a una clase completa si el comportamiento es reutilizable o si desea la capacidad de intercambiar el comportamiento en tiempo de ejecución.Podemos usar las clases como están ahora, pero hay algunas mejoras que podemos hacer a la
CardVisitorinterfaz. Por ejemplo, puede llegar un momento en queCards pueda tener tres, cuatro o cinco caras. En lugar de agregar nuevos métodos para implementar, podríamos tener el segundo método take y array en lugar de dos parámetros. Esto tiene sentido si las tarjetas de varias caras se tratan de manera diferente, pero el número de caras por encima de una se trata de manera similar.También podríamos convertir
CardVisitora una clase abstracta en lugar de una interfaz, y tener implementaciones vacías para todos los métodos. Esto nos permite implementar solo los comportamientos que nos interesan (tal vez solo nos interesan los s de una sola caraCard). También podemos agregar nuevos métodos sin obligar a cada clase existente a implementar esos métodos o no compilar.fuente