Tabla de contenidos
El objetivo general de esta práctica es repasar el uso de la herencia en Java y su aplicación a arrays de objetos. La herencia la basaremos en el uso de abstract
y extends
, con las que crearemos una clase genérica y varias específicas que deriven de ella. Se implementarán sus métodos abstractos y se sobreescribirán otros que permitirán demostrar el polimorfismo a la hora de usar los objetos de la jerarquía de clases. Por último, usaremos un array de objetos de la anterior jerarquía que permita demostrar todo lo anterior.
En particular, en esta práctica se va a calcular el area total de un conjunto de figuras que hayamos creado previamente. Partiremos de la clase Triangle
y crearemos una clase genérica Figure
que permita abstraernos a la hora de trabajar con cualquier tipo de figura. Una vez definido el comportamiento de Figure
, se derivará nuestra clase Triangle
de ella y se adaptará convenientemente para que pueda ser considerada como una Figure
. Después crearemos la clase Square
que heredará también de Figure
, como era de imaginar.
CUIDADO: la geometría de los triángulos y cuadrados es más complejade modelar de lo que puede parecer en un primer vistazo. Si intentáramos resolverlas, perderías la mayor parte del tiempo de la práctica peleando con la geometría en lugar de con la programación. Por lo tanto, vamos a hacer dos simplificaciones: una respecto al cálculo del área del triángulo y otra respecto a la forma del cuadrado. Es decir, la práctica que vamos a hacer es matemáticamente incorrecta. Ambas simplificaciones se explicarán cuando llegue el caso, pero ten en cuenta que en un ejercicio real, en lugar de una práctica como esta, sí tendrías que hacer unas matemáticas correctas.
Una vez creadas todas las figuras, implementaremos una clase que contenga un array de figuras (un triángulo y un cuadrado, por ejemplo) y calcularemos el area total de ellas aprovechando el polimorfismo de las clases para calcular el area de cada una.
Figure
y Triangle
La clase Figure
representa una figura genérica que se materializará posteriormente
en una específica (Triangle
, Square
, etc.). En Java, esto lo representamos mediante las clases
abstractas que se declaran mediante la palabra reservada abstract
.
Una clase abstracta es la que declara uno o más métodos abstractos cuya implementación se realiza en las clases derivadas. Repasa la teoría si no recuerdas este concepto.
El primer ejercicio de la práctica consiste en definir la siguiente clase abstracta Figure
, que representa una figura genérica:
public abstract class Figure { /** Name of the figure */ String name; /** Constructor of the figure with a name */ public Figure(String name) { } /** Calculates the area of a figure */ abstract public double area(); /** Indicates if the figure is regular or not */ abstract public boolean isRegular(); }
Comienza descargando el siguiente código
Figure.java
.
Observa que se han declarado dos métodos abstractos que se implementarán en las clases derivadas correspondientes.
Figure
.Programa el constructor de la clase.
¿qué sentido tiene implementar el constructor de una clase abstracta?
Triangle
.La clase Triangle
ahora deriva de Figure
y tienes que modificarla para que refleje dichos cambios.
Para ello, parte de la clase Triangle
que puedes encontrar en el siguiente enlace
Triangle.java
y realiza los
siguientes cambios:
Necesitarás tener acceso a la clase Point
para poder compilar la clase Triangle
.
Puedes descargarla del siguiente enlace Point.java
.
Declara la clase Triangle
de la siguiente forma:
public class Triangle extends Figure
Modifica los constructores para que en la primera línea llamen al constructor de la clase base Figure
mediante la referencia super
.
Implementa los métodos que se especifican a continuación:
El método area()
que permite calcular el area del triángulo.
El prototipo del método se muestra a continuación:
public double area() { /* complete */ }
El area del tríangulo se calcula según la siguiente fórmula:
Area = ( base X altura ) / 2
Usa el método distance(Point anotherPoint)
de la clase
Point
con los vértices del triángulo.
Para calcular la altura de un triángulo cualquiera es necesario aplicar trigonometría: aquí puedes aproximar como si fuera un triángulo isósceles o equilatero y suponer que la proyección del vértice opuesto es el punto medio de la base.
El método isRegular()
que permite saber si un triángulo es regular.
El prototipo del método se muestra a continuación:
public boolean isRegular() { /* complete */ }
El método debe devolver true
si la longitud de todos los lados
del triángulo es la misma.
Solo necesitas comparar la longitud de los tres lados del triángulo.
El método toString()
que permite obtener una representación en modo
texto del objeto. El prototipo del método se muestra a continuación:
public String toString() { /* complete */ }
El método devuelve una cadena de caracteres con los valores del del triángulo según un determinado formato. El formato de salida de la cadena de caracteres del método es el siguiente (los valores mostrados son sólo ejemplos):
TRIANGLE [NAME=triangle1] [NON REGULAR] : VERTEXES (1.0, 1.0),(3.0, 1.0),(2.0, 2.0)
Por favor, recuerda que lo que se muestra es la cadena de salida particularizada con valores ejemplo de los atributos de la clase para que simplemente compruebes el formato de salida. En el método no tienes que usar estos valores sino que tienes que trabajar con los atributos directamente.
Usa el método toString()
de los vertices del triángulo.
Recuerda que el operador "+", aplicado a cadenas de texto, permite concatenarlas.
main
de la clase Triangle
Ahora debes crear un método para probar el código anterior. Crea el método main en la
clase Triangle
que haga lo siguiente:
Crear tres puntos.
Crear un triángulo a partir de los puntos anteriores.
Imprimir por pantalla una cadena descriptiva con los valores del triángulo.
Por último, imprime el valor del area del triángulo.
Square
.La clase Square
hereda también de la clase Figure
y tiene el
siguiente aspecto:
public class Square extends Figure { /** Square vertexes */ private Point vertex1; private Point vertex2; private Point vertex3; private Point vertex4; /** Constructor with name and vertexes */ public Square(String name, Point diagonalVertex1, Point diagonalVertex3) { } /** Private method to calculate the vertexes for the other diagonal*/ private void otherDiagonal(Point vertex1, Point vertex3) { } /** Method implementation to calculate the area */ public double area() { return 0; } /** Implementation of the abstract method to calculate if the figure is regular. */ public boolean isRegular() { return false; } /** Returns a representative string of the square. */ public String toString() { return null; } }
Observa que un cuadrado se crea a partir de una de sus diagonales, por ello el constructor recibe
como parámetros los dos vértices de dicha diagonal. Para calcular los otros dos vértices restantes, has de
implementar el método privado otherDiagonal(Point vertex1, Point vertex3)
que calcula y crea los otros dos
vértices de la segunda diagonal del cuadrado a partir de los vértices que le pasas por parámetros. Es decir,
crean los vértices vertex2
y vertex4
del cuadrado.
Observa también que al derivar de una clase abstracta como Figure
, la clase
Square
está obligada a implementar TODOS sus métodos abstractos... ¿cuáles son?
Square
.Descarga el esqueleto de la clase Square
del siguiente enlace
Square.java
y
realiza las siguientes tareas para implementa el constructor de la clase.
Implementa primero el siguiente método privado que permite crear los vértices de la segunda diagonal a partir de los dos vértices de la primera. El prototipo del método se muestra a continuación:
private void otherDiagonal(Point vertex1, Point vertex3) { /* complete */ }
Ese método crea los dos vértices de la segunda diagonal del cuadrado (vertex2
y
vertex4
) en base a las coordenadas de los vértices de la primera diagonal que se le pasa como parámetros.
Esta es otra de la simplificaciones que decíamos. Para una solución correcta, deberías encontrar el vector perpendicular a la diagonal definida y entonces buscar los puntos que se encuentran a la distancia adecuada. Pero, para que no tengas que estas peleando con las matemáticas, te proponemos lo siguiente: simplemente supón que los lados son paralelos a los ejes, de modo que la x y la y de los dos vértices que faltan son simplemente los de los puntos ya conocidos, pero cruzados entre sí.
Implementa el constructor de la clase.
Usa el método que has implementado anteriormente para calcular los vértices del cuadrado.
Square
.Implementa los métodos de Square
que se especifican a continuación:
Programa el método area()
que permite calcular el área del cuadrado.
El prototipo de dicho método se muestra a continuación:
public double area() { /* complete */ }
El método permite calcular el área de un cuadrado de acuerdo a la siguiente fórmula: Area = base X altura
Usa el método distance(Point anotherPoint)
de la
clase Point
con los vértices del cuadrado.
Programa en método isRegular()
que permite comprobar si un cuadrado
es una figura regular. El prototipo de dichos métodos se muestra a continuación:
public boolean isRegular() { /* complete */ }
El método es obvio.
Programa en método toString()
que permite obtener una representación
en modo texto del objeto. El prototipo de dicho método se muestra a continuación:
public String toString() { /* complete */ }
El método devuelve una cadena de caracteres con formato con los valores
del cuadrado. El formato de salida de la cadena de caracteres del método
toString()
es el siguiente (los valores son ejemplos):
SQUARE [NAME=cuadrado1] : VERTEXES (3.0, 3.0),(5.0, 3.0),(5.0, 5.0),(3.0, 5.0)
Puedes reutilizar gran parte del método toString()
del triángulo.
Recuerda que el operador "+", aplicado a cadenas de texto, permite concatenarlas.
main
de la clase Square
Ahora debes crear un método para probar el código anterior. Crea el método main
en la clase Square
que haga lo siguiente:
Crear dos puntos que serán la diagonal de tu figura.
Crear un cuadrado a partir de los puntos anteriores.
Imprimir por pantalla una cadena descriptiva con los valores del cuadrado.
Por último, imprime el valor del area del cuadrado.
La clase FiguresArea
, que contiene el array de figuras con el que trabajaremos en este ejercicio, es la que nos va a permitir calcular el area total de un conjunto de figuras. Tiene el siguiente aspecto:
public class FiguresArea { /** The array of figures */ private Figure figures[] = null; /** Constructor of the class with a fixed number of figures. */ public FiguresArea(int figuresNumber) { } /** Calculates the total area of the array figures. */ public double totalArea() { return 0.0; } /** Adds a new figure in the first empty position of the figures array. */ public void addFigure (Figure f){ } /** Prints a list with the array figures. */ public void print() { } /** Main Program */ public static void main(String args[]) throws Exception { } }
El constructor de la clase inicializa el array con el número máximo de figuras. El método
addFigure()
permite añadir un objeto Figure
en el primer hueco libre del array. El método
totalArea()
es el que irá sumando las areas de las figuras que contiene el array
y devolverá la suma total de ellas. El método print()
saca por pantalla dicha area
total con algún mensaje descriptivo de las figuras.
Descarga la clase FiguresArea
del siguiente enlace
FiguresArea.java
y realiza la siguiente
tarea
Implementa el constructor de la clase, que inicializa el array de figuras con el número máximo.
FiguresArea
.Implementa los métodos de FiguresArea
que se especifican a continuación:
Programa en método addFigure (Figure f)
que permite añadir un objeto Figure
al array.
El prototipo de dicho método se muestra a continuación:
public void addFigure (Figure f) { /* complete */ }
El método debe añadir un nuevo objeto Figure
al array de figuras.
Recordando de teoría, Figure
es abstracta y, por consiguiente
no podrían existir instancias de dichos objetos. Las instancias son, en realidad, de las clases
derivadas de Figure
, es decir: Triangle, Square
.
Programa en método totalArea()
que permite calcular el area total de las figuras del array.
El prototipo de dichos métodos se muestra a continuación:
public double totalArea() { /* complete */ }
El método debe calcular el area total de las figuras contenidas en el array de las figuras. Para ello, recorre el array de las figuras y va sumando las areas correspondientes de cada una de ellas..
Recuerda para qué servían los metodos abstractos de Figure
a la hora de
implementar el método.
Programa en método print()
que permite imprimir el area total de las figuras. El prototipo
de dichos métodos se muestra a continuación:
public void print() { /* complete */ }
El método imprime por pantalla el resultado del cálculo del area de todas las figuras. Para ello, hace uso del método anterior que permite calcularla.
Puedes imprimir también de qué figura se trata, para lo cual, usa los métodos
toString()
de cada figura.
El polimorfismo es una consecuencia de aplicar la herencia en las clases y permite lo que se conoce como sobreescritura de métodos por la que un mismo método puede implementarse en una clase base y derivada. En tiempo de ejecución, en función del tipo del objeto, el intérprete de Java invocará al de la clase correspondiente del tipo del mismo.
main
de la clase FiguresArea
Crea el programa principal que tiene que realizar lo siguiente:
Crear un objeto FiguresArea
con dos figuras como máximo.
Crear una figura que sea un Triangle
. Para ello tendras que crear previamente
los 3 puntos que constituyen los vértices del mismo y pasárselos al constructor.
Crea una figura que sea un Square
. Igualmente, tendrás que crear previamente
los 2 puntos que constituyen la diagonal del cuadrado y pasarlos a su constructor.
Añade el triangulo y el cuadrado al array de figuras de FiguresArea
. Usa el método
que has implementado anteriormente para ello.
Imprime el resultado del calculo del area total de las figuras. Para ello, invoca al método correspondiente de la clase.
Mediante este ejercicio se pretende que el alumno conozca el funcionamiento básico de las referencias a clase base en las jerarquías de herencia.
Descargue los siguientes ficheros java
(
Position.java
,
Figure.java
,
Castle.java
,
Queen.java
)
y analice la jerarquía de clases y la clase Position
utilizadas.
Vamos a manejar una colección de elementos figuras. Como la clase Figure
es abstracta no podremos crear instancias de ella, por lo que la colección de elementos estará formada por objetos de las clases Castle y Queen
, indistintamente.
Implemente el código necesario para mantener en la misma colección de forma indiferente a 2 objetos de la clase Queen
y a 4 objetos de la clase Castle
.
Una vez implementado el código anterior, recorra la colección llamando al método public void whoAmI()
para comprobar el funcionamiento correcto de las referencias a clase base.
Una vez realizado el ejercicio se plantean una serie de cuestiones para resolver:
¿Sobre la colección de elementos del array a qué métodos puede llamarse realmente?
Si la clase Castle
implementase el método void castle()
¿podría llamarse a este método desde una referencia a clase base?
¿Qué tendríamos que hacer para poder utilizar el método anterior void castle()
en un objeto de la clase Castle
que es apuntado por una referencia a la clase Figure
?
¿Qué tendríamos que hacer para saber exactamente a que clase pertenecen cada uno de los objetos apuntados por una referencia a clase base?
En este ejercicio se van a asentar los conceptos de Programación Orientada a Objetos (POO) en Java, aplicándolos a una jerarquía de clases para definir figuras geométricas.
NOTA IMPORTANTE: Recuerda que es fundamental que vayas probando el código según vas implementando cada método, aunque no se pida explícitamente.
Figure
.En Java, una Interfaz representa las especificaciones que cumplen todas las clases que lo implementan. Se utilizan habitualmente para definir el conjunto de métodos que queremos asegurarnos que proporcionen dichas clases.
Por ejemplo,
la interfaz Comparable
representa en Java los objetos que pueden compararse entre sí. Por tanto, define el
método compareTo()
que permite comparar dos objetos.
Cualquier clase cuyos objetos se puedan comparar entre sí, debería implementar este método. Así se
facilita el diseño y la reutilización de código, puesto que siempre sabemos cómo se llamará el
método de comparación y qué resultado devolverá.
Antes de seguir con el resto de apartados, responde a las siguientes cuestiones:
¿Qué es una interfaz?
¿Cómo se implementa en java?
¿Qué significa que una clase implementa una interfaz? ¿Cómo se indica en java?
Todas las clases que se definan en este ejercicio deben proporcionar un conjunto
de métodos básicos, como calcular el área de la figura geométrica representada. En Java,
esto se implementa definiendo la interfaz que declara todos los métodos que obligatoriamente
deben implementarse en dichas clases. Por tanto, todas las clases creadas en este ejercicio,
deben implementar la siguiente
interfaz Figure
.
Una clase abstracta es una clase que no puede ser instanciada. Se indica con el modificador abstract
en su declaración. Una clase es abstracta cuando tiene "al menos" algún método abstracto. Es decir, un método que únicamente está declarado, pero no implementado. Este método tendrá que programarse en las clases hijas. Puesto que no pueden instanciarse objetos, las clases abstractas tienen sentido como clases de las que heredan otras.
Recuerda que una clase abstracta puede tener constructores, aunque no pueden instanciarse objetos de la misma.
Programa la clase abstracta GeometricFigure
, que almacenará la información común a todas las figuras geométricas (por ejemplo, una etiqueta identificativa) y proporcionará los métodos que puedan programarse independientemente de la forma concreta de la figura. Esta clase debe implementar la interfaz Figure
. Todas las clases que representen figuras geométricas heredarán de la clase GeometricFigure
.
Cualquier figura tendrá una etiqueta identificativa. Por tanto deberás definir un atributo de tipo String
en esta clase para almacenar dicha etiqueta.
Programa un constructor que reciba como parámetro la etiqueta identificativa de la figura.
Programa los métodos get
y set
para modificar el atributo etiqueta. Puesto que estos métodos no deberían cambiar su funcionalidad, decláralos como final
para evitar que las clases hijas los sobreescriban.
Implementa el método printDescription()
. Observa que no es un método abstracto, aunque invoque otros que sí lo sean. Para evitar que las clases hijas puedan modificar el formato de la descripción, decláralo como final
.
El método debe imprimir por pantalla una descripción de la figura, incluyendo su etiqueta, el tipo de figura y su área con el siguiente formato:
Tag: C-5 Figure Type: Square Area: 25
Recuerda que esta clase debe implementar la interfaz Figure
, por lo que debe proporcionar todos los métodos definidos en dicha interfaz que puedan implementarse con la información disponible en la clase: getTag
e printDescription
. Los métodos que no puedan implementarse, no es necesario incluirlos (Java asume automáticamente que son abstractos), pero deberán proporcionarlos las clases hijas.
La siguiente clase a implementar representará un rectángulo y, cómo no, se llamará Rectangle
. Esta clase hereda de GeometricFigure
e implementa la interfaz Figure
. Para caracterizar el rectángulo se utilizarán su base y su altura, que asumiremos que son números enteros.
Programa la declaración de la clase y sus atributos correspondientes.
Programa el constructor y los métodos accesores (get
y set
) básicos.
Programa los siguientes métodos de la clase que se especifican a continuación:
public String getFigureType(); public double area(); public void drawTxt();
El método drawTxt()
debe dibujar la figura en la consola de texto. Por ejemplo, un rectángulo con base 6 y altura 3 se podría mostrar como:
****** ****** ******
Descarga la clase FiguresTesting
, y haz que en su método main cree una instancia de la clase Rectangle
y la muestre en consola, junto con su descripción.
Contesta a las siguientes cuestiones:
Indica la diferencia entre una clase y un objeto
¿Qué pasos tiene el proceso de instanciar un objeto?
¿Cómo se instancia en java un objeto?
RECUERDA: Si una clase implementa una interfaz, automáticamente todas sus clases hijas implementan dicha interfaz, incluso aunque no se indique explicítamente en su declaración. Observa que la clase Rectangle
implementa la interfaz Figure
, aunque no lo indiques explícitamente en su declaración, porque hereda de una clase que lo implementa Figura
.
Un cuadrado es un rectángulo con base y altura iguales. En Java, podemos crear una clase Square
muy fácilmente, mediante herencia, a partir de la clase Rectangle
.
Programa la clase Square
de tal forma que herede de la clase Rectangulo
del apartado anterior. La clase Square
sólo necesita un constructor que reciba un parámetro (el valor del lado), y que llame al constructor de Rectangle
con los parámetros base y altura iguales.
Prueba esta nueva clase añadiendo el código necesario a la clase FiguresTesting
.
Observa que gracias a la herencia, pueden invocarse los métodos programados en la clase Rectangle
sobre un objeto de tipo Square
, sin necesidad de reprogramarlos.
Contesta a las siguientes cuestiones:
¿En qué consiste la herencia?
¿Cómo se indica en java que una clase hereda de otra?
¿Qué métodos de la clase padre son visibles desde la clase hija?
¿En qué consiste la sobreescritura (overrid) de métodos?
Recuerda que, a diferencia del resto de métodos, las clases hijas no heredan automáticamente los constructores de la clase padre, pero pueden invocarse con ayuda de super()
.
En este apartado debes mejorar la clase FiguresTesting
para que proporcione una interfaz de usuario en modo texto, que permita al usuario elegir la figura geométrica que desea pintar, solicite los parámetros adecuados para cada figura, instancie la clase, imprima su descripción y pinte la figura en consola.
Utiliza el siguiente menú:
1.- Create rectangle 2.- Create square 3.- Display figure 0.- Exit
Si el usuario selecciona una de las 2 primeras opciones, deberán pedírsele los datos adecuados. Si selecciona la opción 3, se mostrará la descripción y se dibujará la última figura creada. Debe presentarse el menú hasta que el usuario seleccione la opción Salir.
Para facilitar la generación del objeto adecuado a partir de los datos que introduzca el usuario, conviene que añadas un método readFigureData()
a cada una de las clases programadas. Este método recibe como parámetro un objeto de tipo BufferedReader
, del cual irá leyendo los datos que introduzca el usuario. Y devuelve como resultado un objeto de la clase correspondiente inicializado con los datos introducidos. Decláralo como método estático.
Contesta a las siguientes cuestiones:
¿De qué tipo son los objetos instanciados (en las opciones 1 y 2)?
¿De qué tipo es la variable que los referencia?
¿Qué métodos de la clase padre son visibles desde la clase hija?
¿Puedes utilizar la misma varible como referencia a las distintas figuras creadas? ¿Por qué?
Mediante este ejercicio pretendemos dar una visión de cómo la orientación a objetos nos permite reutilizar código siempre que la utilicemos correctamente.
Supongamos que sabemos muy poco sobre teoría de cifrado de cadenas de caracteres. Digamos que conocemos de oídas varios algoritmos de cifrado, en su mayoría difíciles de implementar y uno, llamado Cesar, basado en una rotación en caracteres que es asequible de implementar por nosotros.
El algoritmo de César se basa en el orden de los caracteres en el abecedario. Para cada uno de los caracteres de la cadena de entrada se le suman N posiciones en el abecedario para encontrar el caracter por el que sustituir. Por ejemplo, si nos encontramos con el caracter 'A' y hay que sumar 3 posiciones, sustituiríamos la 'A' por la 'D' al cifrar ese caracter.
Implementa una solución orientada a objetos que me permita fácilmente en el futuro cambiar este algoritmo por otro mucho mejor.
Haz primero una solución basada en una clase abstracta Cifrado
que representa un algoritmo genérico de cifrado, con las operaciones básicas de cifrado y descifrado (por ejemplo, recibiendo una cadena y devolviendo una cadena), y luego una clase CifradoCesar
que deriva de la primera e implementa el algoritmo de César.
En segundo lugar, implementa una solución basada en la definición de una interfaz Cifrable
y una clase CifradoCesar
que lo implementa.