java - sirve - ¿cuando se puede usar el constructor super()?
¿Crear una nueva clase de objeto o escribir un método que convierta objetos de subclase? ¿o algo mas? Rendimiento NO Preferencia (3)
Con respecto a esos modelos de dominio, estoy de acuerdo con Simon Forsberg en que son demasiado complicados y entiendo que están diseñados para demostrar algunos conceptos, pero incluso entonces podrían haber usado un ejemplo más realista, como los componentes de un automóvil y sus relaciones. Probablemente solo necesite dos clases de dominio, Tarjeta y Jugador. Para una colección ordenada de Tarjetas, use una List<Card>
. Cuando se trata de tratar, tomar turnos, etc., hay mucho espacio para la creatividad sobre cómo organizar la lógica del juego. En general, es preferible componer tipos a partir de otros en lugar de depender en gran medida de la herencia, que es menos flexible en la práctica.
Cuando se trata de rendimiento, se aplican reglas comunes. Por ejemplo, reutilizar objetos cuando sea posible. Si siempre estás usando el mazo de 52 cartas, entonces no hay razón para tener una clase de Cartas. Probablemente no haya nada mejor que reutilizar los valores de enumeración en todas partes.
enum Card {
ACE_CLUBS,
TWO_CLUBS,
// ...
ACE_DIAMONDS,
// ...
}
(Realmente no puede hacer que esta enumeración sea más complicada, ya que los diferentes juegos usan diferentes ordenamientos, colocan diferentes valores en diferentes tarjetas según el contexto, etc.)
Para obtener una excelente guía práctica de programación para el rendimiento, recomiendo el libro Java Performance: The Definitive Guide .
He reunido dos programas separados que juegan un juego de cartas llamado ''Crazy Eights''.
Las clases que he escrito para este programa se basan en un paquete predeterminado de "cartas" que proporciona objetos de cartas de juego y algunos métodos genéricos para jugar cartas.
He tomado dos enfoques separados para lograr esto que son funcionales por derecho propio.
Aquí hay dos diagramas de clase UML que representan los dos enfoques:
Método de ''conversión'' de subclase heredado
Subclase compuesta con métodos similares.
Como puede ver en el enfoque 1, la clase EightsCard contiene un método convert (Tarjeta) Aquí está el método:
/**
* Converts a Card into an EightsCard
* @param card The card to be converted
* @return The converted EightsCard
*/
public EightsCard convert(Card card) {
if (card != null) {
EightsCard result = new EightsCard(card.getRank(), card.getSuit());
return result;
}
return null;
}
}
Este método le permite llamar a los métodos de CardCollection que de otro modo no serían legales. Por ejemplo, en el método de juego de la clase EightsPlayer que se muestra a continuación:
/**
* Removes and returns a legal card from the player''s hand.
*/
public EightsCard play(Eights eights, EightsCard prev) {
EightsCard ecard = new EightsCard(0, 0);
ecard = ecard.convert(searchForMatch(prev));
if (ecard == null) {
ecard = drawForMatch(eights, prev);
return ecard;
}
return ecard;
}
El Enfoque 2 no requiere conversiones, ya que los métodos similares se han escrito en una nueva clase EightsCardCollection que extiende CardCollection. Ahora los métodos de juego se pueden escribir así:
public EightsCard play(Eights eights, EightsCard prev) {
EightsCard card = searchForMatch(prev);
if (card == null) {
card = drawForMatch(eights, prev);
}
return card;
}
Esto me lleva a un par de preguntas:
- ¿Hay ventajas para cualquiera de los enfoques más allá de las preferencias personales?
- ¿Hay una mejor manera de componer este programa?
Por ejemplo, podría ser mejor escribir clases ''similares'' que son más específicas 1 y no usar las clases predeterminadas 2 en absoluto.
1 etiquetado ''crazyeights.syd.jjj'' o ''chaptwelvetofort'' en los diagramas de clase.
2 etiquetados ''defaults.syd.jjj'' o cards.syd.jjj ''en los diagramas de clase.
El comentario de Simon me llamó la atención: "Preferiría tener dos enumeraciones, una para Suit y otra para Rank, en lugar de una enumeración para toda la carta"
¿Hay una mejor manera de componer este programa?
El punto clave es este: la mayor parte de su programa debe estar completamente aislado de la representación en memoria de una tarjeta.
En la memoria, una tarjeta puede ser
- un entero
- un byte
- una enumeración
- una cuerda
- una URL ( http://example.org/cards/hearts/7 )
- un entero / byte restringido al rango [0,52)
- una tupla
- int, int
- enumerar
- byte
- mezclar y combinar
- una referencia a una implementación de terceros
La mayor parte de su código no debería importarle .
Idealmente, los únicos bits que se preocupan en absoluto serían el módulo que proporciona la implementación, y quizás la raíz de la composición.
La mayoría de los lugares en su diagrama donde tiene <<Java Class>>
debería tener en su lugar <<Java Interface>>
.
Ver Parnas, 1972
En ese tipo de diseño, utilizarías la composición para adjuntar tus ocho semánticas locas a una definición genérica de tarjeta definida en otro lugar.
Eights.Deck deck(Iterable<Generic.Card> standardDeck) {
List<Eights.Card> cards = new ArrayList();
for(Generic.Card c : standardDeck) {
cards.add(
new Eights.Card(c)
);
}
return new Eights.Deck(cards);
}
o quizás
Eights.Deck deck(Generic.Deck standardDeck) {
return new Eights.Deck(
standardDeck
.stream()
.map(Eights.Deck::new)
.collect(Collectors.toList())
);
}
La raíz de la composición se vería algo así como
public static void main(String [] args) {
GenericCards genericCards = new ApacheGenericCards();
EightsCards eightsCards = MyAwesomEightsCardsV2(genericCards);
EightsGame game = new EightsGame(eightsCards)
game.play();
}
Demasiadas subclases
Ninguno de estos enfoques es muy bueno, ya que ambos tienen más subclases de las necesarias. ¿Por qué EightsCard
extiende la Card
? ¿Y si quisieras implementar algún otro juego de cartas? (Hay bastantes, ya sabes ...) ¿Harías una subclase para cada juego de cartas? Por favor no lo hagas
Tendría estas clases:
- Tarjeta
- Tarjeta de recogida
- Jugador
Ni siquiera tendría una clase Deck
, ya que parece que no hace nada más que extender CardCollection
sin proporcionar más funcionalidad .
Entonces tendría una clase para cada implementación del juego, así que una clase EightsController
que maneja la lógica del juego para ese juego. No hay una clase específica para EightsCard, no hay una clase específica para EightsCardCollection, etc.
La razón es simple: no necesitas nada más que esto. Una tarjeta y una colección de tarjetas es exactamente la misma, independientemente del juego de cartas que estés jugando. Un jugador también es lo mismo en todos los juegos.