Tabla de contenidos
El objetivo general de esta práctica es repasar el uso
de la herencia y el polimorfismo en Java. La herencia la basaremos en
el uso de abstract
y extends
, con los que
crearemos clases genéricas y específicas siguiendo una determinada
jerarquía. Las clases específicas implementarán los métodos abstractos
definidos en las genéricas y sobreescribirán otros métodos que
permitirán demostrar el polimorfismo. La jerarquía de clases de este
ejercicio estará compuesta por la clase Figure
de la que
derivarán las clases Circle
, Triangle
y
Quadrilateral
. Además, de la clase
Quadrilateral
derivará la clase
Rectangle
.
Una vez implementadas todas las clases, se creará un contenedor con objetos de diferentes clases, sobre el cual realizaremos operaciones de forma sencilla aprovechando la potencia del polimorfismo en Java.
La clase Figure
representa una figura
genérica que se materializará posteriormente en una específica
(Circle
, Triangle
,
Rectangle
). En Java, esto se representa mediante las
clases abstractas que se declaran mediante la palabra reservada
abstract
.
El primer ejercicio de la práctica consiste en
definir la siguiente clase abstracta Figure
. Puedes
descargar su código en Figure.java
.
public abstract class Figure { // Name of the figure private String name; // Constructor of the figure 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(); // Gets the name of the figure protected String getName() { } // Sets the name of the figure protected void setName(String name) { } }
Figure
.Programa el constructor de la clase
Figure
y los métodos get y set
correspondientes.
Circle
.La clase Circle
deriva de
Figure
y debes implementarla teniendo en cuenta esta
situación. Realiza las siguientes acciones sobre la clase
Circle:
Declara la clase Circle
para que
herede (extends
) de la clase
Figure
.
Implementa el constructor. Dicho constructor
debe recibir tres parámetros, un nombre (name
) de
tipo String
, un centro (center
) de
tipo Point
, y un radio (radius
) de
tipo int
. Utiliza la clase Point.java
y llama al constructor de la clase base Figure
mediante la referencia super
en la implementación
del constructor de la clase Circle
.
Implementa el método area()
que
devuelva el área del círculo. Haz uso de Math.PI
para el cálculo del área.
Implementa el método isRegular()
que devuelva true
si la figura es regular y
false
en caso contrario. Ten en cuenta que un
círculo siempre es una figura regular.
Sobreescribe el método toString()
para que devuelva el nombre de la figura, su centro y su
radio.
Implementa los métodos get y set necesarios en esta clase.
Triangle
.La clase Triangle
también deriva de
Figure
. Realiza las siguientes acciones sobre la
clase Triangle.java
Declara la clase Triangle
para
que herede de la clase Figure
.
Implementa el constructor, el cual debe
recibir cuatro parámetros, un nombre (name
) de
tipo String
, y tres puntos (vertex1
,
vertex2
y vertex3
) de tipo
Point
representando los tres vértices del
triángulo. Ten en cuenta que debes llamar al constructor de la
clase base Figure
mediante la referencia
super
.
Implementa el método area()
que
calcule el área del triángulo. Utiliza la siguiente fórmula
area = 0.5*|Ax(By-Cy)+Bx(Cy-Ay)+Cx(Ay-By)|
donde
x
e y
representan las coordenadas en
el eje x y en el eje y de los tres vértices del triángulo
(A,B,C), y |x|
el valor absoluto de
x
.
Implementa el método isRegular()
que indique si el triángulo es regular, es decir si sus tres
lados son iguales.
Sobreescribe el método toString()
para que devuelva el nombre de la figura, y sus
vértices.
Implementa los métodos get y set necesarios en esta clase.
Quadrilateral
.La clase Quadrilateral
es otra clase
que deriva de Figure
. Sin embargo, la clase
Quadrilateral
es una clase abstracta ya que no es
posible implementar el método que calcula el área sin conocer el
cuadrilátero concreto. Realiza las siguientes acciones sobre la
clase Quadrilateral
.
Declara la clase Quadrilateral
para que herede de la clase Figure
.
Implementa el constructor, el cual debe
recibir cinco parámetros, un nombre (name
) de
tipo String
, y cuatro puntos
(vertex1
, vertex2
,
vertex3
y vertex4
) de tipo
Point
representando los cuatro vértices del
cuadrilátero. Ten en cuenta que debes llamar al constructor de
la clase base Figure
mediante la referencia
super
.
Implementa el método isRegular()
que indique si el cuadrilatero es regular, es decir si es un
rectángulo o un cuadrado. Para ello puedes llamar desde el
método isRegular()
al método privado
checkRectangle()
que a su vez invoca
al método privado scalarProduct()
.
El código de ambos métodos se indica al final de este
apartado. ¿Qué es lo que hacen estos métodos?
Sobreescribe el método toString()
para que devuelva el nombre de la figura, y sus
vértices.
Implementa los métodos get y set necesarios en esta clase.
/** * Indicates if the quadrilateral is a rectangle or square by * comparing sizes and diagonals and calculating the scalar product * */ private boolean checkRectangle(Point v1, Point v2, Point v3, Point v4){ Point auxVertex = v1.nearest(new Point[]{v2,v3,v4}); if (auxVertex.equals(v2)){ return v1.distance(v3) == v2.distance(v4) && v1.distance(v4) == v2.distance(v3) && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v3,v4})); } else if (auxVertex.equals(v3)){ return v1.distance(v2) == v3.distance(v4) && v1.distance(v4) == v3.distance(v2) && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v2,v4})); } else if (auxVertex.equals(v4)){ return v1.distance(v2) == v4.distance(v3) && v1.distance(v3) == v4.distance(v2) && scalarProduct(v1,auxVertex,v1.nearest(new Point[]{v2,v3})); } else { return false; } } private boolean scalarProduct(Point p1, Point p2, Point p3){ return (p3.getY()-p1.getY())*(p2.getY()-p1.getY()) + (p3.getX()-p1.getX())*(p2.getX()-p1.getX()) == 0; }
Rectangle
.La clase Rectangle
hereda de
Quadrilateral
. Realiza las siguientes acciones sobre
la clase Rectangle
Declara la clase Rectangle
para
que herede de la clase Quadrilateral
.
Implementa el constructor para que reciba
cinco parámetros, un nombre (name
) de tipo
String
, y cuatro puntos (vertex1
,
vertex2
, vertex3
y
vertex4
) de tipo Point
representando
los cuatro vértices del rectángulo. Ten en cuenta que debes
llamar al constructor de la clase base
Quadrilateral
mediante la referencia
super
. Comprueba como parte del constructor que
el rectángulo efectivamente es un cuadrilátero regular y en
caso contrario lanza una excepción (recomendamos que revises
la documentación sobre excepciones)
Implementa el método area()
que
calcule el área del rectángulo. Para ello debes conocer la
base y la altura. En un rectángulo, dado un vértice
V1
, la distancia con los dos vértices más
cercanos definen la base y la altura del rectángulo.
Sobreescribe el método toString()
para particularizarlo al rectángulo.
La clase FiguresSet
contiene un
ArrayList de Figuras y permitirá calcular el área total de un
conjunto de figuras. Esta clase tiene el siguiente aspecto:
import java.util.ArrayList; public class FiguresSet { // Array of figures private ArrayList<Figure> figures; // Constructor of FiguresSet public FiguresSet() { } // Calculates the total area of the figures public double totalArea() { } // figures to String public String toString() { } // Adds a new figure to the FigureSet public void addFigure(Figure f) { } // Main program public static void main(String args[]) throws Exception { } }
El constructor de la clase debe crear un objeto de
la clase ArrayList
sobre el que se irán añadiendo las
distintas figuras. El método addFigure()
permite
añadir un objeto Figure
en el contenedor. 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 toString()
devuelve un
String
con la información de todas las figuras.
total con algún mensaje descriptivo de las figuras.
Implementa el constructor de la clase.
Programa en método addFigure (Figure
f)
que permite añadir un objeto Figure
al
contenedor.
Programa en método totalArea()
que permite calcular el area total de las figuras del
contenedor.
Programa en método toString()
que
permite devolver la información sobre las figuras incluidas en
el contenedor.
main
de la clase
FiguresSet
Crea el programa principal que tiene que realizar lo siguiente:
Crear un objeto de la clase
FiguresSet
.
Crear un círculo.
Crea un triángulo.
Crea un rectángulo.
Añade el círculo, el triangulo y el rectángulo al objecto de
la clase FigureSet
.
Imprime el resultado del calculo del area total de las figuras y también la información sobre las figuras.
Una de las situaciones en que las interfaces resultan útiles en Java es aquella en que se dispone de varias implementaciones alternativas para proporcionar una misma funcionalidad. En ese caso, es deseable que el resto del programa pueda utilizar cualquiera de estas implementaciones de forma homogénea, sin importar de qué implementación concreta se trate.
En este ejercicio trabajaremos con una situación de este tipo: dispondremos de varias implementaciones distintas para calcular el número pi. Para ello, definimos la siguiente interfaz:
import java.math.BigDecimal; /** * Interface to be implemented by classes that compute the number pi. * */ public interface PiProvider { /** * Computes and returns the value of the number pi. Implementations * may decide the precision with which they compute the value. * * @return The number pi, with the precision each implementation * decides. * */ BigDecimal computePi(); }
Dado que algunas implementaciones son capaces de calcular pi con
la precisión que se les solicite, se define también la siguiente
interfaz, que hereda de PiProvider
y añade varios
métodos nuevos que permiten gestionar la precisión del cálculo,
entendida como el número de dígitos de pi:
import java.math.BigDecimal; /** * Interface to be implemented by classes that compute the number pi. * Classes that implement this interface can provide the value of pi * with the requested precision, understood as the number of exact * digits of the computed value. * */ public interface AdvancedPiProvider extends PiProvider { /** * Sets the desired precision. * * @param precision The desired precision (number of digits). * * @throws PrecisionException if the desired precision is * negative, zero or bigger than the maximum precision * this class can provide. * */ void setPrecision(int precision) throws PrecisionException; /** * Returns the current value of precision. * * @return The current value of precision (number of digits). * */ int getPrecision(); /** * Returns the maximum precision with which this provider is able * to generate the value of pi. * * @return The maximum precision available from this provider, or * Integer.MAX_VALUE if the provider can provide an * arbitrarily big precision. * */ int getMaximumPrecision(); }
Como se puede observar, el método setPrecision
puede
lanzar una excepción si el valor de precisión no es válido (menor
que uno o superior a la máxima precisión a la que la
implementación concreta dé soporte. A continuación se muestra el
código de esta excepción:
public class PrecisionException extends Exception { public PrecisionException(int precision) { super("Unsupported precision: " + precision); } }
Programa y prueba una clase llamada PiSimple
que
implemente la interfaz PiProvider
y devuelva
siempre el valor de pi como 3,14.
Programa y prueba una clase llamada PiFromMath
que
implemente la interfaz PiProvider
y devuelva el
valor Math.PI
convertido a BigDecimal
.
Programa y prueba una clase llamada PiFromBBP
que
implemente la interfaz AdvancedPiProvider
,
basándote en el código de la clase PiCalc
vista en
una práctica previa. Sólo tendrás que hacer algunas pequeñas
adaptaciones a este código para que implemente la interfaz:
import java.math.BigDecimal; import java.math.MathContext; public class PiCalc { private int numDigits; private MathContext mc; public PiCalc(int numDigits) { this.numDigits = numDigits; mc = new MathContext(numDigits); } public BigDecimal compute() { BigDecimal pi = new BigDecimal(0); BigDecimal limit = new BigDecimal(1).movePointLeft(numDigits); boolean stop = false; for (int k = 0; !stop; k++) { BigDecimal piK = piFunction(k); pi = pi.add(piK); if (piK.compareTo(limit) < 0) { stop = true; } } return pi.round(mc); } private BigDecimal piFunction(int k) { int k8 = 8 * k; BigDecimal val1 = new BigDecimal(4); val1 = val1.divide(new BigDecimal(k8 + 1), mc); BigDecimal val2 = new BigDecimal(-2); val2 = val2.divide(new BigDecimal(k8 + 4), mc); BigDecimal val3 = new BigDecimal(-1); val3 = val3.divide(new BigDecimal(k8 + 5), mc); BigDecimal val4 = new BigDecimal(-1); val4 = val4.divide(new BigDecimal(k8 + 6), mc); BigDecimal val = val1; val = val.add(val2); val = val.add(val3); val = val.add(val4); BigDecimal multiplier = new BigDecimal(16); multiplier = multiplier.pow(k); BigDecimal one = new BigDecimal(1); multiplier = one.divide(multiplier, mc); val = val.multiply(multiplier); return val; } public static void main(String[] args) { if (args.length != 1) { System.err.println("One command-line argument expected: number of " + "digits."); } else { PiCalc piCalc = new PiCalc(Integer.parseInt(args[0])); System.out.println(piCalc.compute()); } } }
Al contrario que en el código de partida, el constructor de la nueva clase no debe recibir parámetros. Establecerá un valor por defecto de precisión de 30 dígitos.
Programa y prueba una clase llamada PiStored
que
almacene un valor precalculado de pi como cadena de
texto. Cuando se le solicite computar el valor, simplemente
devolverá la subcadena pertinente (según la precisión que se
haya establecido). La precisión máxima vendrá determinada por el
número de caracteres de la cadena de texto que almacenes
internamente.
El constructor no debe recibir parámetros. Establecerá un valor por defecto de precisión de 30 dígitos.
Puedes declarar el valor de pi con la siguiente cadena:
private static final String PI = "3.14159265358979323846264338327950" + "2884197169399375105820974944592307" + "8164062862089986280348253421170679";
Para quedarte con un trozo de la cadena, puedes utilizar el
método substring
de la clase
String
.
Programa y prueba una clase llamada Circle
que:
BigDecimal
.
area
que recibe un
objeto que implemente la interfaz PiProvider
y
devuelve al área del círculo calculada mediante el valor de pi
que proporcione dicho objeto.
Utiliza adecuadamente el polimorfismo para que esta clase sea
capaz de utilizar cualquier implementación de
PiProvider
, previendo que en el futuro podrían
crearse nuevas implementaciones distintas a las que ya has
desarrollado. Prueba el código utilizando instancias de cada una
de las implementaciones que has hecho.