Programación 3

Universidad de Alicante, 2023–2024

Práctica 4

Polimorfismo y herencia

Peso relativo de está práctica en la nota de prácticas: 20%

Formas 2D

Proyecto base

Descarga el proyecto base para Eclipse de esta práctica, que ya contiene algunas clases y test unitarios. Impórtalo como proyecto Java en Eclipse.

Introducción

En esta práctica haremos uso de conceptos estudiados en las unidades 6 y 7 (sobrescritura, covarianza, clases abstractas,…).

Se trata de implementar el modelo base de una típica aplicación de dibujo geométrico en dos dimensiones, que nos permita crear figuras geométricas básicas. El paquete es.ua.dlsi.prog3.p4.gui, que se da ya implementado, contiene el código cliente del modelo que debemos implementar, que se encontrará en el paquete es.ua.dlsi.prog3.p4.model.

Al realizar tu implementación, ten en cuenta los diferentes niveles de visibilidad de cada elemento (atributos y métodos) tal y como se representan en el diagrama UML. Esto es especialmente importante cuando se trabaja con herencia.

Aquí tienes el diagrama UML de estas clases (se omite el paquete es.ua.dlsi.prog3.p4.gui):

Figura 1. Diagrama de clases UML.
Figura 1. Diagrama de clases UML.

El proyecto Eclipse ya contiene las clases del paquete gui y la clase Coordinate del paquete model, así como algunos test unitarios. Al principio tendrás errores de compilación en el paquete gui, es normal, ya que depende del paquete model; desaparecerán conforme vayas implementando este último.

La parte del paquete model que debes implementar es la jerarquía de clases que forman Form2D y sus subclases AbstractPolygon, Circle, Rectangle y Square, la clase factoría Form2DFactory, encargada de crear objetos de la jerarquía y la clase Canvas que modela un lienzo sobre el cual se pintarán las figuras.

Paquete es.ua.dlsi.prog3.p4.model

(Recuerda que Coordinate ya se da implementada. No debes modificarla.)

Coordinate

Esta clase representa una coordenada bidimensional. En ella se definen tres constantes:

  • NOT_VALID : indica un valor numérico no válido para las componentes de una coordenada.
  • MAX_VALUE : valor máximo positivo que puede tomar la componente de una coordenada (32767.9999).
  • MIN_VALUE : valor mínimo negativo que puede tomar la componente de una coordenada (-32767.9999).

Dispones de tres constructores para Coordinate: el constructor por defecto, el de copia y el sobrecargado. El constructor por defecto asigna valores no válidos a las componentes de la coordenada. El constructor sobrecargado permite construir coordenadas con valores iniciales dados, siempre y cuando estén dentro del rango de valores máximo y mínimo. Si no es así, este constructor lanzará la excepción no verificada IllegalArgumentException.

También dispones de un par de getters para obtener los valores de las componentes de la coordenada, el método toString() para obtener una representación como cadena de ésta y el método equals() que devolverá cierto cuando ambas componentes de las coordenadas que se comparen sean iguales. Además tienes los dos siguientes métodos para operar con coordenadas:

  • sum(Coordinate) : obtiene una nueva coordenada resultado de sumar otras dos:
Coordinate c1, c2;
...
Coordinate c3 = c1.sum(c2);
  • subtract(Coordinate) : funciona del mismo modo que el método anterior, pero devuelve una coordenada resultado de restar otras dos.

Ambas operaciones pueden producir coordenadas con valores fuera de rango. En ese caso lanzarán una excepción no verificada de tipo ArithmeticException.

Form2D

Esta clase abstracta es la raíz de la jerarquía de clases mediante la cual modelamos figuras bidimensionales. Como puedes deducir echando un rápido vistazo al diagrama UML, las figuras de nuestro modelo tienen una posición en el plano, especificada mediante un objeto Coordinate; esta propiedad tiene su respectivo getter. Además, estas figuras se pueden trasladar (move()), escalar (scale()) y clonar (clone()).

Constructores:

  • Form2D() : Crea una figura con una posición no válida.
  • Form2D(Coordinate) Crea una figura con la posición dada.
  • Form2D(Form2D) : Constructor de copia.

Métodos:

  • getPosition(): getter para obtener la posición de una figura.
  • move(Coordinate) : traslada una figura a la nueva posición dada por el argumento y devuelve la antigua posición de la figura. Si el argumento es null no modifica la posición y simplemente devuelve la posición actual.
  • equals(Object) : devuelve cierto si las figuras comparadas tienen posiciones iguales.
  • toString() : devuelve una representación en forma de cadena de la figura, con este formato:
(<x>,<y>)

donde <x> e <y> son las componentes de la posición de la figura. Por ejemplo, para una figura que esté en la posición (-30.4, 27.1):

(-30.4,27.1)
  • clone() : método abstracto que deben implementar las subclases de Form2D y que devuelve una copia de la figura.
  • clone(Coordinate) : devuelve una copia de la figura, trasladada a la posición especificada por el argumento.
  • scale(double) : método abstracto que deben implementar las subclases de Form2D para escalar las dimensiones de una figura. El porcentaje de escalado se especifica mediante el argumento, con respecto a las dimensiones actuales de la figura y puede tener cualquier valor positivo mayor que cero. Por ejemplo, un valor 100 indica mantener inalteradas las dimensiones de la figura. Un valor 50 indicaría escalar a la mitad las dimensiones y un valor 200 indicaría multiplicar por dos las dimensiones de la figura. Esta operación no altera la posición de una figura. Lanza la excepción IllegalArgumentException si el porcentaje indicado es negativo o cero.

No debes preocuparte del caso en el que a una figura se le asigne una posición no válida (un objeto Coordinate cuyas componentes sean Coordinate.NOT_VALID). Esto está permitido. De hecho, es lo que hace el constructor por defecto de esta clase.

Circle

Subclase de Form2D que modela un círculo. El centro del círculo corresponde a la posición de la figura (en Form2D) y su radio está especificado por el atributo radius, que no puede ser negativo.

Constructores:

  • Circle() : crea un círculo de radio cero y sin centro definido
  • Circle(Circle) : constructor de copia
  • Circle(Coordinate, double) : crea un círculo con el centro y radio dados como argumentos. Lanza IllegalArgumentException si el radio indicado es negativo.

Métodos:

  • getRadius() : getter para el radio.
  • scale(double) : implementa el método scale() de Form2D. Un círculo se escala modificando su radio en el porcentaje indicado por el argumento (ver Form2D.scale() para más detalles).
  • clone() : devuelve una copia de este círculo.
  • toString() : devuelve una cadena que representa el estado del objeto Circle, con este formato:
(<x>,<y>),radius=<r>

donde <x> e <y> son las componentes de la posición de la figura y <r> el valor de su radio. Por ejemplo, para un círculo que esté en la posición (-30.4, 27.1), con radio 3.5:

(-30.4,27.1),radius=3.5
  • equals(Object) : devuelve cierto si el objeto pasado como argumento es un círculo y tiene el mismo centro y radio que este círculo.

AbstractPolygon

Esta clase abstracta hereda de la clase Form2D y es la clase base de todos los polígonos que se modelan en nuestra aplicación. Los polígonos de nuestro modelo tienen, además de los atributos heredados de Form2D, un ángulo que indica la rotación del polígono; esta propiedad tiene su respectivo getter. Además, estas figuras se pueden rotar (rotate()). Fíjate que aunque esta clase es abstracta no define nuevos métodos abstractos, pero hereda los métodos abstractos de Form2D y no los implementa.

Constructores:

  • AbstractPolygon() : Crea un polígono con un ángulo de rotación 0 y posición no definida.
  • AbstractPolygon(Coordinate,double) Crea un polígono con la posición y ángulo de rotación dados. Lanza la excepción IllegalArgumentException si el ángulo indicado no se encuentra en el intervalo [0.0,360.0).
  • AbstractPolygon(AbstractPolygon) : Constructor de copia.

Métodos:

  • getAngle(): getter para obtener el ańgulo de rotación de un polígono.
  • equals(Object): devuelve cierto si los polígonos comparados tienen posiciones y ángulos iguales.
  • toString(): devuelve una representación en forma de cadena del polígono, con este formato:
(<x>,<y>),angle=<angle>

donde <x> e <y> son las componentes de la posición del polígono y <angle> su ángulo de rotación. Por ejemplo, para una figura que esté en la posición (-30.4, 27.1) y cuyo ángulo de rotación sea 45.0°:

(-30.4,27.1),angle=45.0
  • rotate(double): rota un polígono el número de grados dado como argumento, cuyo valor deberá estar en el intervalo (-360.0,360.0). En caso contrario debe lanzar la excepción IllegalArgumentException. La rotación se realiza sumando al ańgulo del polígono el ángulo recibido como argumento. Ten en cuenta que el ángulo de rotación resultante deberá encontrarse en el intervalo [0.0,360.0); de modo que si, por ejemplo, el ángulo del polígono es 45.0° y la rotación recibida como argumento es -90.0°, el ángulo resultante deberá ser 315.0°.

Rectangle

Constructores:

  • Rectangle() : Crea un rectángulo con ángulo de rotación igual a cero, de base y altura igual a cero, y posición sin definir.
  • Rectangle(Rectangle) : constructor de copia
  • Rectangle(Coordinate, double, double, double) : crea un rectángulo con la posición indicada por el primer argumento, el ángulo indicado como segundo argumento, y la base (length) y la altura (width) indicadas en el tercer y cuarto argumento, respectivamente. La posición del rectángulo es la de su vértice superior izquierdo. Lanza IllegalArgumentException si la base o la altura indicadas son negativas.

Métodos:

  • getLength() y getWidth() : getters para la base y la altura, respectivamente.
  • scale(double) : implementa el método scale() de Form2D. Un rectángulo se escala modificando su base y altura en la proporción indicada por el argumento (ver Form2D.scale() para más detalles).
  • clone() : devuelve una copia de este rectángulo.
  • toString() : devuelve una cadena que representa el estado del objeto rectangle, con este formato:
(<x>,<y>),angle=<a>,length=<l>,width=<w>

donde <x> e <y> son las componentes de la posición de la figura, <a> es su ángulo de rotación, <l> y <w> son el valor de su base y su altura, respectivamente. Por ejemplo, para un rectángulo que esté en la posición (-30.4, 27.1), con base 2.5, altura 7.0 y ángulo 45.0°:

(-30.4,27.1),angle=45.0,length=2.5,width=7.0
  • equals(Object) : devuelve cierto si el objeto pasado como argumento es un rectángulo y tiene la misma posición, ángulo, base y altura que este rectángulo.

Square

Constructores:

  • Square() : Crea un cuadrado de lado igual a cero, ángulo de rotación igual a cero y posición sin definir.
  • Square(Square) : constructor de copia
  • Square(Coordinate, double, double) : crea un cuadrado con la posición indicada por el primer argumento, el ángulo de rotación indicado en el segundo argumento y el lado (side) indicado como último argumento. La posición del cuadrado es la de su vértice superior izquierdo. Lanza IllegalArgumentException si el lado indicado es negativo.

Métodos:

  • getSide(): getter para el lado.
  • scale(double) : implementa el método scale() de Form2D. Un cuadrado se escala modificando su lado en la proporción indicada por el argumento (ver Form2D.scale() para más detalles).
  • clone() : devuelve una copia de este cuadrado.
  • toString() : devuelve una cadena que representa el estado del objeto square, con este formato:
(<x>,<y>),angle=<a>,side=<s>

donde <x> e <y> son las componentes de la posición de la figura, <a> es su ángulo de rotación y <s> el valor de su lado. Por ejemplo, para un cuadrado que esté en la posición (-30.4, 27.1), con lado 2.5 y ángulo 45.0°:

(-30.4,27.1),angle=45.0,side=2.5
  • equals(Object) : devuelve cierto si el objeto pasado como argumento es un cuadrado y tiene la misma posición, ángulo y lado que este cuadrado.

Canvas

Canvas representa un lienzo que contiene figuras geométricas bidimensionales. Un lienzo tiene un título y unas dimensiones (cuyo valor por defecto es DEFAULT_SIZE), especificadas por los atributos width (anchura) y height (altura). El lienzo contiene una lista de figuras de tipo Form2D.

Constructores:

  • Canvas() : crea un lienzo sin figuras, con las dimensiones por defecto y título "default canvas".
  • Canvas(Canvas) : constructor de copia
  • Canvas(String, double, double) crea un lienzo vacío con un título y la anchura (width) y altura (height) indicadas por el segundo y tercer argumento, respectivamente. Lanza IllegalArgumentException si la anchura o altura indicadas son negativas.

Métodos:

  • addForm(Form2D) : añade una figura al lienzo. No realiza ninguna comprobación respecto si la posición de la figura está dentro del lienzo o no.
  • clone() : Devuelve una copia de este lienzo.
  • getWidth() y getHeight() : getters para la anchura y altura del lienzo, respectivamente.
  • getNumForms() : devuelve el número de figuras que han sido añadidas al lienzo.
  • getForm(int) : devuelve la figura añadida en n-ésima posición al lienzo, empezando en uno. Lanza IndexOutOfBoundsException si el índice dado no está entre 1 y el número total de figuras añadidas al lienzo.
  • removeForm(int) : elimina la figura añadida en n-ésima posición al lienzo, empezando en uno. Lanza IndexOutOfBoundsException si el índice dado no está entre 1 y el número total de figuras añadidas al lienzo.
  • toString() : devuelve una cadena con el título, dimensiones del lienzo y el número de figuras que contiene. Por ejemplo, si tenemos el lienzo con título “Prog3”, de anchura 1024 y altura 768, con dos círculos y un rectangulo, este método devolverá una cadena como ésta:
Prog3 (1024.0,768.0) with 3 forms

Form2DFactory

Clase factoría cuya responsabilidad es la de crear figuras de la jerarquía Form2D.

  • createForm2D(String) : crea y devuelve la figura solicitada usando el constructor por defecto de su clase. El parámetro del método contendrá el nombre de la clase de figura a crear (Circle, Square o Rectangle). Lanza la excepción IllegalArgumentException si el nombre de la figura no es Circle, Square o Rectangle.

Paquete es.ua.dlsi.prog3.p4.gui

Este paquete contiene dos clases que puedes usar para probar tu implementación. Para que funcione debes primero completar tu implementación, obviamente. Ambas clases tiene un método main(), por lo que puedes ejecutarlas por separado.

La clase PaintApp utiliza la clase Canvas para crear un lienzo y añadirle figuras, cuya descripción luego mostrará por la salida estándar.

La clase SwingPaint es una pequeña aplicación gráfica que permite dibujar figuras en pantalla usando el ratón. Primero debes seleccionar el tipo de figura a dibujar (círculo, rectángulo o cuadrado) y luego pinchar y arrastrar el ratón para dibujar la figura. Una vez tengas figuras en el lienzo, puede usar los botones [Fill circles] o [Fill Rects & Sq.] para rellenar (o vaciar) todos los círculos o todas las figuras rectangulares con un mismo color. También puedes usar el botón [Clear] para borrar todas las figuras del lienzo.

Video demo de SwingPaint

Pruebas unitarias

Proporcionamos pruebas unitarias en la carpeta test/ del proyecto base que comprueban el adecuado comportamiento de las clases. Es importante que entiendas qué se prueba y cómo se hace.

Documentación

Este apartado no se realizará en el control

Debes incluir en los ficheros fuente todos los comentarios necesarios en formato javadoc. Estos comentarios deben definirse para:  

  • Ficheros: debe incluir nombre y dni de los autores usando la anotación @author
  • Clases: propósito de la clase: mínimo 3 líneas
  • Operaciones/métodos: una línea de descripción para funciones triviales, y un mímimo de dos líneas para funciones más complejas. Parámetros de entrada, de salida y excepciones que lanza.
  • Atributos: propósito de cada uno de ellos: como mínimo 1 línea  

Puedes usar un comentario no javadoc cuando sea necesario.

No es necesario generar en HTML la documentación javadoc.

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. Evita también los mensajes por la salida de error.
  • Se debe respetar de manera estricta el formato del nombre de todas las propiedades 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.

Entrega de la práctica

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

Debes subir allí un archivo comprimido con tu código fuente (sólo archivos .java). En un terminal, sitúate en el directorio ‘src’ de tu proyecto Eclipse e introduce la orden

tar czvf prog3-p4.tgz *

Esto comprimirá todo el código que hay en src/, incluyendo el de aquellas clases que ya se daban implementadas. Esto es correcto y debes entregarlo así.

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

Esta entrega sólo sirve para que se evalue la parte de documentación y obtener el resultado del oráculo.

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 Clase(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”.


Aclaraciones

  • Aunque no se recomienda, 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. 
  • Cualquier aclaración adicional aparecerá en este enunciado.