Programación 3

Universidad de Alicante, 2021–2022

Práctica 5

Plazo de entrega: Hasta el domingo 26 de diciembre de 2021 a las 23:59h
Peso relativo de está práctica en la nota de prácticas: 10%
IMPORTANTE: Todos tus ficheros de código fuente deben utilizar la codificación de caracteres UTF-8. No entregues código que use otra codificación (como Latin1 o iso-8859-1).

Aclaraciones

  • Actualizadas las pruebas unitarias debido a un bug en SuperFighter.java (16/12/2021, 18:07)

ImperialCommander: reflexión y genericidad

Introducción

El diseño propuesto para la práctica anterior tenía un problema que se resolverá en la actual: la factoría de cazas no estaba completamente abierta a la extensión, ya que añadir un nuevo tipo de caza implicaría añadir una nueva instrucción condicional o una nueva cláusula case a una instrucción switch. En esta práctica, este problema se arreglará introduciendo convenientemente la reflexión. Además, el modelo se ampliará con un sistema de clasificación que será capaz de reflejar las puntuaciones obtenidas durante la cada partida y clasificarlas de acuerdo con dos criterios diferentes: la cantidad de victorias y el valor de los diferentes cazas destruidos.

Diagrama de clases

El siguiente diagrama de clases UML representa las clases de nuestro modelo. Solamente se muestran las clases nuevas (que pertenecen todas al paquete model.game.score), y los cambios en las clases de la práctica anterior.

Diagrama de clases UML.
Diagrama de clases UML.

 

Hoja de ruta

El orden aconsejado para ir construyendo tu práctica es:

  • Introduce la reflexión en la clase FighterFactory; puedes probarlo añadiendo una nueva subclase de Fighter en model.fighters y modificando el MainP4min.java para que la use. Una vez lo hayas probado, borra la subclase que has añadido
  • Crea el paquete model.game.score e implementa las clases de ese paquete: Score, WinsScore, DestroyedFightersScore y Ranking (tendrás que añadir el método getValue a la clase Fighter)
  • Añade el parámetro Fighter al método Ship.updateResults (no debe hacer nada con él), y actualiza las llamadas a ese método en Board (debes pasar como parámetro el caza que resulta destruido en la pelea)
  • Modifica la clase GameShip para que cree las puntuaciones y las actualice en la llamada a updateResults (tendrás que sobreescribir ese método)
  • Introduce los getters de las puntuaciones en IPlayer y las clases que lo implementan
  • Modifica el método Game.play (con ayuda de un método privado) para que en cada paso se creen los rankings y se muestren.

Recuerda, ve diseñando pruebas unitarias según vayas terminando cada clase, y utilízalas para depurar tu código antes de pasar a la siguiente clase.

Cambios en las clases de la práctica anterior

Los cambios que debes hacer en las clases de la práctica anterior son:

  • En la clase Ship, hay que añadir un argumento Fighter al método updateResults; tendrás que actualizar las llamadas a ese método en Board para que pasen como parámetro el caza destruido

  • En la clase Fighter debes añadir un método getValue, que debe devolver el valor del caza. Por simplificar, vamos a asumir que el valor del caza es la suma de su velocidad y su ataque

  • En GameShip tendrás que añadir los atributos de las puntuaciones (WinsScore y DestroyedFightersScore), los getters para esos atributos, y tendrás que añadir un método updateResults que sobreescriba el de la clase padre, que debe hacer lo mismo (llamando al método de la clase padre) y además, cuando el resultado sea 1, debe actualizar las puntuaciones llamando al método score para cada una de ellas

  • En IPlayer, PlayerFile y PlayerRandom sólo tienes que añadir los getters nuevos, que simplemente llamarán al getter correspondiente de GameShip

  • En Game.play hay que modificar el bucle de la partida para que muestre al principio de cada jugada los rankings. Para ello es recomendable usar un método privado que cree los rankings (como variables locales), les añada las puntuaciones de cada jugador y los muestre por pantalla (en las salidas de los programas principales puedes ver el formato de salida). Además, cuando acabe el bucle, antes de devolver el ganador, debe mostrar otra vez los rankings, para que aparezcan justo al final.


Reflexión

Tienes que reemplazar las instrucciones condicionales o el switch existente en FighterFactory.createFighter(-) con código que utilice la reflexión para crear una instancia de un objeto cuyo nombre de clase coincida con el parámetro type; tendrás que calificar completamente el nombre de la clase a cargar añadiendo convenientemente el nombre del paquete, es decir, si el nuevo tipo es FWing, el nombre de la clase será model.fighters.FWing

El método createFighter devolverá null como en la práctica anterior si el parámetro no contiene un nombre válido de caza; esta circunstancia se detectará cuando se lance alguna de las excepciones de los métodos de la API de reflexión, que deben capturarse e ignorarse para al final devolver null

El método createFighter está ahora abierto a la extensión (podemos añadir tipos de cazas sin modificar su código) pero cerrados a la modificación. Los nuevos tipos de cazas podrán utilizarse en la clase PlayerFile, pero no en PlayerRandom, que debe usar solamente los tipos de cazas de la práctica 3.

NOTA: si no lo has hecho ya, te recomendamos que visualices los vídeos sobre reflexión que hay en el Moodle de teoría de la asignatura

Sistema de clasificación y genericidad

Clase Score

La nueva clase abstracta Score encapsula el concepto de la puntuación de una partida. Como el objetivo es crear un sistema de clasificación, las puntuaciones deben ser comparables. Esto se logrará haciendo que Score implemente la interfaz de Java Comparable, en particular Comparable<Score<T>>, lo que obliga a la clase a implementar el método compareTo(-):

public abstract class Score<T> implements Comparable<Score<T>>{   
   ...   
}

El tipo T determinará el tipo de puntuación a definir, que será Integer para WinsScore, o Fighter para DestroyedFightersScore.

Las subclases no abstractas de Score implementarán el método score(-) que, cuando sea apropiado, incrementa el atributo score; valores más altos de esta puntuación significarán un mejor rendimiento del jugador. El atributo score se utilizará en el método Score.compareTo(-) para comparar dos puntuaciones diferentes.

Método Score(Side)

Constructor que almacena el bando recibido como argumento e inicializa el atributo score a cero.

Método toString()

Devuelve una representación en forma de cadena de caracteres del score (sin ‘\n’ al final). Para ello toma el bando del atributo side y la puntuación del atributo score. Ejemplo de salida para un jugador imperial con puntuación 7:

Player IMPERIAL: 7 

Método compareTo(Score<T>)

Este método compara dos puntuaciones por su valor. Como queremos implementar un orden descendente de las puntuaciones para la clasificación, dados dos objetos de tipo Score<T>, el que represente la mayor puntuación será considerado el más pequeño. Si ambos objetos tienen la misma puntuación el método devolverá lo que devuelva compareTo(·) al comparar los side de las puntuaciones (el bando IMPERIAL aparecerá antes que el bando REBEL). Como resultado obtendremos un orden de las puntuaciones: de la mayor a la menor. Echa un vistazo a la descripción del método compareTo(-) en la documentación de la API de Java 8 para saber lo que compareTo(-) debe devolver en cada caso (aunque igual lo recuerdas de la práctica 2).

Clase WinsScore

Esta clase representa un score basado en Integer y simplemente lleva la cuenta de las victorias obtenidas por la nave del jugador. Debe ser declarado como sigue:

public class WinsScore extends Score<Integer> {
   ...
}

Método WinsScore(Side)

Constructor que simplemente pasa el argumento recibido al constructor de su superclase.

Método score(Integer)

Este método incrementa el atributo score en 1 si el entero recibido como argumento es 1.

Clase DestroyedFightersScore

Esta clase representa una puntuación basada en Fighter y suma el valor de los diferentes cazas destruidos por la nave del jugador. Se declara de forma análoga a la de WinsScore pero usando Fighter en lugar de Integer.

Método DestroyedFightersScore(Side)

Constructor que simplemente pasa el argumento recibido al constructor de su superclase.

Método score(Fighter)

Este método incrementa el atributo score con el valor del caza recibido como argumento (si no es null), usando el método Fighter.getValue() descrito anteriormente.

Clase Ranking

Para tener un sistema de clasificación basado en las puntuaciones obtenidas por los diferentes jugadores, se añadirá una nueva clase RankingRanking es una clase genérica que tiene un parámetro de tipo restringido para evitar que puntuaciones de diferentes tipos coexistan en una clasificación particular:

public class Ranking<ScoreType extends Score<?>> {
   ...
}

Por lo tanto, un objeto de tipo Ranking tendrá que ser creado de una de dos maneras, ya sea como

Ranking<WinsScore> rh = new Ranking<>();

o como

Ranking<DestroyedFightersScore> rc = new Ranking<>();

Por consiguiente, un error del compilador impedirá que el código del cliente añada puntuaciones de diferentes criterios al mismo objeto de clasificación (pruébalo cuando ya tengas estas clases implementadas). Nótese que a partir de Java 7 los argumentos de tipo requeridos para invocar al constructor de una clase genérica pueden ser reemplazados por un conjunto vacío (el par de corchetes angulares se llama el diamante) siempre que el compilador pueda inferir los tipos a partir del contexto.

Método Ranking()

Este método sólo inicializa el atributo scoreSet (de clase TreeSet); scoreSet debe ser declarado de la siguiente manera:

private SortedSet<ScoreType> scoreSet;

y se inicializa así en el constructor:

scoreSet = new TreeSet<>();

Asegúrate de que entiendes las líneas anteriores.

Método addScore(ScoreType)

Este método añade el objeto pasado como parámetro al conjunto scoreSet. Obsérvese que scoreSet es un conjunto ordenado y, en consecuencia, se invocará el método compareTo(-) de Score para tener ordenados los elementos del conjunto.

Método getWinner()

Devuelve el primer elemento del conjunto scoreSet.

Excepciones: Si el conjunto está vacío, la excepción RuntimeException será lanzada.

Método Ranking.getSortedRanking()

Un simple getter.

toString()

Devuelve una cadena formada por las puntuaciones del ranking, rodeada con |. Por ejemplo, para un ranking de WinsScore en el que el jugador rebelde lleva 7 victorias y el imperial 6, debería devolver:

| Player REBEL: 7 | Player IMPERIAL: 6 |

Ten en cuenta que la cadena Player REBEL: 7 se genera en el método toString de la clase Score

Programa principal

Los programas principales de la práctica 4 MainP4.java y MainP4min.java son perfectamente válidos para esta práctica, lo que cambia es la salida de ambos por la introducción de los rankings: salida-MainP4-p5.txt y salida-MainP4min-p5.txt


Pruebas unitarias

El archivo prog3-ImperialCommander-p5-pretest.tgz contiene una carpeta pretestp5 con un paquete model con pruebas unitarias, algunas de ellas usan ficheros que están en la carpeta files, como en la práctica anterior.

Documentación

Debéis incluir en los ficheros fuente todos los comentarios necesarios en formato Javadoc. Estos comentarios deben definirse para:  

  • Ficheros: debe incluir el nombre y dni del autor de la práctica usando la anotación @author
  • Clases: propósito de la clase
  • Métodos: propósito del método + parámetros de entrada (@param), valor de retorno (@return) y funciones dependientes para operaciones más complejas, y excepciones cuando corresponda (@throws).
  • Atributos: propósito de cada uno de ellos

No se debe entregar los ficheros HTML que genera Javadoc, y tampoco debes generar la documentación automáticamente con plugins, es una buena práctica generar la documentación a mano, justo en el momento en que se empieza a escribir el método.

Estructura de paquetes y directorios

En esta práctica crearemos un nuevo paquete:

  • model.game.score para las clases relacionadas con las puntuaciones

Requisitos mínimos para evaluar la práctica

  • La práctica debe poder ejecutarse sin errores de compilación. 
  • Ninguna operación debe emitir ningún tipo de comentario o mensaje por salida estándar, a menos que se indique lo contrario. Evitad también los mensajes por la salida de error. 
  • Se debe respetar de manera estricta el formato del nombre de todas las propiedades (públicas, protegidas y privadas) de las clases, tanto en cuanto a ámbito de visibilidad como en cuanto a tipo y forma de escritura. En particular se debe respetar escrupulosamente la distinción entre atributos de clase y de instancia, así como las mayúsculas y minúsculas en los identificadores.
  • La práctica debe estar suficientemente documentada, de manera que el contenido de la documentación que se genere mediante la herramienta javadoc sea significativo.  
  • Se pueden añadir los atributos y métodos privados que se considere oportuno a las clases. No obstante, eso no exime de implementar TODOS los métodos presentes en el enunciado, ni de asegurarse de que funcionan tal y como se espera, incluso si no se utilizan nunca en la implementación de la práctica. 

Entrega de la práctica

La práctica se entrega en el servidor de prácticas del DLSI.

Debes subir allí un archivo comprimido que incluya el directorio model y toda la estructura de directorios y ficheros fuente tal y como se especifica en la práctica. En un terminal, sitúate en el directorio ‘src’ de tu proyecto Eclipse e introduce la orden

tar czvf prog3-imperialCommander-p5.tgz model

Sube este fichero prog3-imperialCommander-p5.tgz al servidor de prácticas. Sigue las instrucciones de la página para entrar como usuario y subir tu trabajo.

Evaluación 

La corrección de la práctica es automática. Esto significa que se deben respetar estrictamente los formatos de entrada y salida especificados en los enunciados, así como la interfaz pública de las clases, tanto en la signatura de los métodos (nombre del método, número, tipo y orden de los argumentos de entrada y el tipo devuelto) como en el funcionamiento de éstos. Así, por ejemplo, el método model.Coordinate(int,int) debe tener exactamente dos argumentos de tipo int.

Tienes más información sobre el sistema de evaluación de prácticas en la ficha de la asignatura.

Además de la corrección automática, se va a utilizar una aplicación detectora de plagios. Se indica a continuación la normativa aplicable de la Escuela Politécnica Superior de la Universidad de Alicante en caso de plagio:

“Los trabajos teórico/prácticos realizados han de ser originales. La detección de copia o plagio supondrá la calificación de”0" en la prueba correspondiente. Se informará la dirección de Departamento y de la EPS sobre esta incidencia. La reiteración en la conducta en esta u otra asignatura conllevará la notificación al vicerrectorado correspondiente de las faltas cometidas para que estudien el caso y sancionen según la legislación vigente".