Universidad Carlos III de Madrid

Ingeniería de Telecomunicación

Enero-Mayo 2010 / January-May 2010

Orientación a Objetos y Herencia

Lab Section1. Sesión 6 (laboratorio): Orientación a Objetos y Herencia (III)

Exercise Section1.1. Puntos y Figuras Geométricas (II)

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.

Herencia. Las clases 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.

Nota

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.

Nota

Observa que se han declarado dos métodos abstractos que se implementarán en las clases derivadas correspondientes.

Constructores de la clase Figure.
  1. Programa el constructor de la clase.

    Cuestion

    ¿qué sentido tiene implementar el constructor de una clase abstracta?

La subclase 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:

Nota

Necesitarás tener acceso a la clase Point para poder compilar la clase Triangle. Puedes descargarla del siguiente enlace Point.java.

  1. Declara la clase Triangle de la siguiente forma:

     public class Triangle extends Figure
  2. Modifica los constructores para que en la primera línea llamen al constructor de la clase base Figure mediante la referencia super.

  3. 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

      Pista 1

      Usa el método distance(Point anotherPoint) de la clase Point con los vértices del triángulo.

      Pista 2

      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.

      Pista

      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.

      Pista 1

      Usa el método toString() de los vertices del triángulo.

      Pista 2

      Recuerda que el operador "+", aplicado a cadenas de texto, permite concatenarlas.

El método 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:

  1. Crear tres puntos.

  2. Crear un triángulo a partir de los puntos anteriores.

  3. Imprimir por pantalla una cadena descriptiva con los valores del triángulo.

  4. Por último, imprime el valor del area del triángulo.

Herencia. La clase 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;
    }
}

Nota 1

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.

Nota 2

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?

Los constructores de la clase 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.

  1. 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.

    Pista

    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í.

  2. Implementa el constructor de la clase.

    Pista

    Usa el método que has implementado anteriormente para calcular los vértices del cuadrado.

Métodos auxiliares de la clase Square.

Implementa los métodos de Square que se especifican a continuación:

  1. 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

    Pista

    Usa el método distance(Point anotherPoint) de la clase Point con los vértices del cuadrado.

  2. 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.

  3. 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)

    Pista 1

    Puedes reutilizar gran parte del método toString() del triángulo.

    Pista 2

    Recuerda que el operador "+", aplicado a cadenas de texto, permite concatenarlas.

El método 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:

  1. Crear dos puntos que serán la diagonal de tu figura.

  2. Crear un cuadrado a partir de los puntos anteriores.

  3. Imprimir por pantalla una cadena descriptiva con los valores del cuadrado.

  4. Por último, imprime el valor del area del cuadrado.

Arrays y polimorfismo. Cálculo del area de varias figuras.

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.

Constructor.

Descarga la clase FiguresArea del siguiente enlace FiguresArea.java y realiza la siguiente tarea

  1. Implementa el constructor de la clase, que inicializa el array de figuras con el número máximo.

Métodos auxiliares de la clase FiguresArea.

Implementa los métodos de FiguresArea que se especifican a continuación:

  1. 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.

    Recuerda

    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.

  2. 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..

    Pista

    Recuerda para qué servían los metodos abstractos de Figure a la hora de implementar el método.

  3. 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.

    Nota

    Puedes imprimir también de qué figura se trata, para lo cual, usa los métodos toString() de cada figura.

    Recuerda

    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.

El método main de la clase FiguresArea

Crea el programa principal que tiene que realizar lo siguiente:

  1. Crear un objeto FiguresArea con dos figuras como máximo.

  2. 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.

  3. 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.

  4. Añade el triangulo y el cuadrado al array de figuras de FiguresArea. Usa el método que has implementado anteriormente para ello.

  5. Imprime el resultado del calculo del area total de las figuras. Para ello, invoca al método correspondiente de la clase.

Soluciones

Las soluciones del ejercicio se pueden ver en el siguiente listado:

import java.io.BufferedReader;
import java.io.InputStreamReader;

/*
 * Figure.java
 *
 * (c) DIT-UC3M 2008
 *
 */

public abstract class Figure {

  /** Name of the figure */
  String name;

  /** Constructor of the figure with a name */
  public Figure(String name) {
    this.name = name;
  }

  /** Calculates the area of a figure */
  abstract public double area();

  /** Indicates if the figure is regular or not */
  abstract public boolean isRegular();

}

	  
// Point class
// (c) 2008 IT

public class Point {
  private double x;
  private double y;

  // Constructor
  public Point(double x, double y) {
    this.x = x;
    this.y = y;
  }

  /**
   * Returns the coordinates of the point in a string
   */
  public String toString() {
    // '+' operator in String objects does not mean mathematical sum, but
    // concatenation of strings
    return "(" + x + ", " + y + ")";
  }

  /**
   * Returns the distance to the origin It can be also calculated calling to
   * distance(new Point(0,0))
   */
  public double distanceToOrigin() {
    return Math.sqrt(x * x + y * y);
  }

  /* Getter methods */
  public double getX() {
    return x;
  }

  public double getY() {
    return y;
  }

  /** Returns the distance to another point */
  public double distance(Point anotherPoint) {
    double x1;
    double y1;

    x1 = x - anotherPoint.getX();
    y1 = y - anotherPoint.getY();
    return Math.sqrt(x1 * x1 + y1 * y1);
  }

  /** Returns the quadrant */
  public int quadrant() {
    if (x > 0.0 && y > 0.0) {
      return 1;
    } else if (x < 0.0 && y > 0.0) {
      return 2;
    } else if (x < 0.0 && y < 0.0) {
      return 3;
    } else if (x > 0.0 && y < 0.0) {
      return 4;
    } else {
      // (x==0.0 || y==0.0)
      return 0;
    }
  }

  /**
   * Returns the nearest point of the array in the parameter or null if array is
   * empty
   * 
   * @param otherPoints
   * @return
   */
  public Point nearest(Point[] otherPoints) {
    Point nearestPoint = null;
    double minDistance;
    double distance;

    if (otherPoints != null && otherPoints.length > 0) {
      // Start storing the first point in the array as the nearest one
      minDistance = distance(otherPoints[0]);
      nearestPoint = otherPoints[0];

      for (int i = 1; i < otherPoints.length; i++) {
        // If nearer point is found, this is the new nearest one
        distance = distance(otherPoints[i]);
        if (distance < minDistance) {
          distance = minDistance;
          nearestPoint = otherPoints[i];
        }
      }
    }

    return nearestPoint;
  }
}

	  
/**
 * Triangle.java
 * 
 * (c) DIT-UC3M 2008
 * 
 */

public class Triangle extends Figure {

  /** Vertexes of the triangle */
  private Point vertex1;
  private Point vertex2;
  private Point vertex3;

  /** Constructor of a triangle with their vertexes */
  public Triangle(String name, Point vertex1, Point vertex2, Point vertex3) {
    super(name);
    this.vertex1 = vertex1;
    this.vertex2 = vertex2;
    this.vertex3 = vertex3;

  }

  /** Returns an array with the lenght of every edge of the triangle */
  public double[] edgesLength() {
    double edgesLength[] = new double[3];

    // Length 1->2
    edgesLength[0] = vertex1.distance(vertex2);
    // Length 2->3
    edgesLength[1] = vertex2.distance(vertex3);
    // Length 3->1
    edgesLength[2] = vertex3.distance(vertex1);

    return edgesLength;
  }

  /** Implementation of the abstract method to calculate the triangle area. */
  public double area() {
    // The area is ( base x height ) / 2
    // We have to calculate the medium point from the base
    // to calculate the height
    // WARNNING: this an aproximation, the correct mathematical are as a bit
    // more complex.
    Point medium = new Point((vertex1.getX() + vertex2.getX()) / 2,
        (vertex1.getY() + vertex2.getY()) / 2);
    double base = vertex1.distance(vertex2);
    double height = medium.distance(vertex3);
    return (base * height) / 2;
  }

  /**
   * Implementation of the abstract method to indicate if the triangle is
   * regular.
   */
  public boolean isRegular() {
    double edgesLength[] = edgesLength();
    if (edgesLength[0] == edgesLength[1] && edgesLength[1] == edgesLength[2]
        && edgesLength[2] == edgesLength[0]) {
      return true;
    }
    return false;
  }

  /** Returns a representative string of the triangle. */
  public String toString() {
    String s = "TRIANGLE";
    s += " [NAME=" + name + "]";
    if (isRegular()) {
      s += " [REGULAR] ";
    } else {
      s += " [IRREGULAR] ";
    }
    s += " : " + "VERTEXES " + vertex1.toString() + "," + vertex2.toString()
        + "," + vertex3.toString();
    return s;
  }

  public static void main(String[] args) {
    Point p1 = new Point(1, 1);
    Point p2 = new Point(3, 1);
    Point p3 = new Point(2, 3);
    Triangle t = new Triangle("Triangle1", p1, p2, p3);
    System.out.println(t.toString());
    System.out.println("Area: " + t.area());
  }
}

	  
/*
 * Square.java
 *
 * (c) DIT-UC3M 2008
 *
 */

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) {
    super(name);
    this.vertex1 = diagonalVertex1;
    this.vertex3 = diagonalVertex3;
    otherDiagonal(diagonalVertex1, diagonalVertex3);
  }

  /** Private method to calculate the vertexes for the other diagonal */
  private void otherDiagonal(Point vertex1, Point vertex3) {
    vertex2 = new Point(vertex3.getX(), vertex1.getY());
    vertex4 = new Point(vertex1.getX(), vertex3.getY());
  }

  /** Method implementation to calculate the area */
  public double area() {
    // In a square, the area is the base multiplied by the height
    double base = vertex1.distance(vertex2);
    double height = vertex1.distance(vertex4);
    return base * height;
  }

  /**
   * Implementation of the abstract method to calculate if the figure is
   * regular.
   */
  public boolean isRegular() {
    // Due to our simplification, the square may not be actually a square, but a
    // rectangle. However, that is just our simplification, so we will return
    // always true: a square is always regular, by definition
    return true;
  }

  /** Returns a representative string of the square. */
  public String toString() {
    String s = "SQUARE";
    s += " [NAME=" + name + "]";
    s += " : " + "VERTEXES " + vertex1.toString() + "," + vertex2.toString()
        + "," + vertex3.toString() + "," + vertex4.toString();
    return s;
  }

  public static void main(String[] args) {
    Point p1 = new Point(1, 3);
    Point p2 = new Point(3, 1);
    Square s = new Square("Square1", p1, p2);
    System.out.println(s.toString());
    System.out.println("Area: " + s.area());
  }

}

	  
import java.io.BufferedReader;
import java.io.InputStreamReader;

/*
 * FiguresArea.java
 *
 *
 * (c) DIT-UC3M 2008
 *
 */

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) {
    figures = new Figure[figuresNumber];
  }

  /** Calculates the total area of the array figures. */
  public double totalArea() {
    double totalArea = 0;
    for (int i = 0; i < figures.length; i++) {
      System.out.println(" Summing area of figure " + i + ":"
          + figures[i].area());
      totalArea += figures[i].area();
    }
    return totalArea;
  }

  /** Adds a new figure in the first empty position of the figures array. */
  public void addFigure(Figure f) {
    for (int i = 0; i < figures.length; i++) {
      if (figures[i] == null) {
        figures[i] = f;
        break;
      }
    }
  }

  /** Prints a list with the array figures. */
  public void print() {
    for (int i = 0; i < figures.length; i++) { 
	/* 
	 * The call to the toString method works because 
	 * this method is defined in the Object class 
	 * (which all Java classes extend).
	 * In general, we need to declare the method in the parent class
	 * in order to be able to call it applying polymorphism.
	 */
	System.out.println(figures[i].toString());
    }
    System.out.println("Total area: "+totalArea());
  }

  /** Main Program */
  public static void main(String args[]) throws Exception {
    FiguresArea figuresArea = new FiguresArea(2);

    Point p1 = new Point(1, 1);
    Point p2 = new Point(3, 1);
    Point p3 = new Point(2, 3);
    Triangle t = new Triangle("Triangle1", p1, p2, p3);

    Point p4 = new Point(1, 3);
    Point p5 = new Point(3, 1);
    Square s = new Square("Square1", p4, p5);

    figuresArea.addFigure(t);
    figuresArea.addFigure(s);
    
    figuresArea.print();
  }
}

	  

Homework Section2. Actividades para casa

Exercise Section2.1. Herencia y referencias a clase base

Objetivo

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.

Ejercicio

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.

Solución

La solución se puede ver en el siguiente listado:

/** Testing class */
public class MainEn {
  public static int MAX_FIGURES = 6;

  // Testing method
  public static void main(String args[]) {
    Figure figures[] = new Figure[MAX_FIGURES];

    // Objects are created
    figures[0] = new Queen(Figure.WHITE);
    figures[1] = new Castle(Figure.WHITE);
    figures[2] = new Castle(Figure.WHITE);
    figures[3] = new Queen(Figure.BLACK);
    figures[4] = new Castle(Figure.BLACK);
    figures[5] = new Castle(Figure.BLACK);

    // Go through the array with the use of references
    // to base class and polymorphism
    for (int i = 0; i < MAX_FIGURES; i++) {
      figures[i].whoAmI();
    }
  }
} // Main

	  

Otras cuestiones

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?

Exercise Section2.2. Ejercicio para practicar con Figuras

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.

La interfaz 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.

La figura geométrica.

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.

  1. 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.

  2. Programa un constructor que reciba como parámetro la etiqueta identificativa de la figura.

  3. 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.

  4. 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.

El rectángulo.

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.

  1. Programa la declaración de la clase y sus atributos correspondientes.

  2. Programa el constructor y los métodos accesores (get y set) básicos.

  3. 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:

    ******
    ******
    ******
    
  4. 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.

  5. 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.

El cuadrado.

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.

  1. 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.

  2. Prueba esta nueva clase añadiendo el código necesario a la clase FiguresTesting.

  3. 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.

  4. 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().

Referencia a interfaces.

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é?

Soluciones

Las soluciones del ejercicio se pueden ver en el siguiente listado:

abstract class GeometricFigure implements Figure {

  private String tag;

  public GeometricFigure(String tag) {
    this.tag = tag;
  }

  public String getTag() {
    return tag;
  }

  public void setTag(String tag) {
    this.tag = tag;
  }

  abstract public String getFigureType();

  abstract public double area();

  abstract public void drawTxt();

  final public void printDescription() {
    System.out.println("Tag: " + getTag());
    System.out.println("Figure Type: " + getFigureType());
    System.out.println("Area: " + area());
    // drawTxt();
  }

}

	  
public interface Figure {

  public String getTag();

  public String getFigureType();

  public double area();

  public void drawTxt();

  public void printDescription();

}

	  
import java.io.*;

public class Rectangle extends GeometricFigure {

  private int base;
  private int height;

  public Rectangle(int base, int height) {
    this(base, height, "Rectangle");
  }

  public Rectangle(int base, int height, String tag) {
    super(tag);
    this.base = base;
    this.height = height;
  }

  public int getBase() {
    return base;
  }

  public void setBase(int base) {
    this.base = base;
  }

  public int getHeight() {
    return height;
  }

  public void setHeight(int height) {
    this.height = height;
  }

  public String getFigureType() {
    return "Rectangle";
  }

  public double area() {
    return (base * height);
  }

  public void drawTxt() {
    for (int row = 1; row <= height; row++) {
      // Print every row with *
      for (int col = 1; col <= base; col++) {
        System.out.print("*");
      }
      // change of line
      System.out.println();
    }

  }

  /**
   * Asks for the necessary data to define a rectangle and generates an object
   * with such data
   */
  public static Rectangle readFigureData(BufferedReader entry) {

    Rectangle r = null;
    int base, height;
    String tag;

    try {
      System.out.print("Enter the base: ");
      System.out.flush();
      base = Integer.parseInt(entry.readLine());

      System.out.print("Enter the height: ");
      System.out.flush();
      height = Integer.parseInt(entry.readLine());

      System.out.print("Enter tag: ");
      System.out.flush();
      tag = entry.readLine();

      r = new Rectangle(base, height, tag);
    } catch (IOException ioe) {
      // Error (in/out):
      // A message is shown and null is returned
      System.err.println("Input/Output errora");
    } catch (NumberFormatException nfe) {
      // Error (invalid data):
      // A message is shown and null is returned
      System.err.println("Invalid data error");
    }

    return r;

  }

}

	  
import java.io.*;

public class Square extends Rectangle {

  public Square(int edge) {
    this(edge, "Square");
  }

  public Square(int edge, String tag) {
    super(edge, edge, tag);
  }

  public String getFigureType() {
    return "Square";
  }

  /**
   * Asks for the necessary data to define a Square and generates an object with
   * such data
   */
  public static Square readFigureData(BufferedReader entry) {
    Square c = null;
    String tag;
    int edge;

    try {

      System.out.print("Enter the edge: ");
      System.out.flush();
      edge = Integer.parseInt(entry.readLine());

      System.out.print("Enter the tag: ");
      System.out.flush();
      tag = entry.readLine();

      c = new Square(edge, tag);

    } catch (IOException ioe) {
      // Error (in/out):
      // A message is shown and null is returned
      System.err.println("Input/Output error");

    } catch (NumberFormatException nfe) {
      // Error (invalid data):
      // A message is shown and null is returned
      System.err.println("Invalid data error");
    }

    return c;
  }

}

	  
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class FiguresTesting {

  /**
   * Basic testing: Generate and show figures
   * 
   * Example of tests to see the classes
   */
  private static void basicTesting() {

    Figure f;

    // Testing rectangle
    f = new Rectangle(10, 5);
    f.printDescription();
    f.drawTxt();

    System.out.println(); // To separate a figure from the other

    // testing Square
    f = new Square(4);
    f.printDescription();
    f.drawTxt();

    System.out.println();
  }

  private static void showMenu() {
    System.out.println();
    System.out.println("1. Create rectangle");
    System.out.println("2. Create square");
    System.out.println("3. See figure");
    System.out.println("0. Exit");
    System.out.print("Select an option > ");
  }

  public static void main(String args[]) {
    // Used for the first part of the test
    // basicTesting();

    Figure f = null;
    String sOption;
    int iOption = 0;

    BufferedReader entry = new BufferedReader(new InputStreamReader(System.in));

    // Program is executed till the user press 0-Exit"
    do {

      // Show the menu
      showMenu();

      // Read the user option
      try {
        sOption = entry.readLine();
        iOption = Integer.parseInt(sOption);
      } catch (IOException ioe) {
        // input/output error
        System.err.println("Input/Output error");
        iOption = 0; // Finish
      } catch (NumberFormatException nfe) {
        // Error: Wrong option
        iOption = -1;
      }

      switch (iOption) {

      case 0:
        // Nothing, do-loop ends
        break;

      case 1: // Rectangle
        f = Rectangle.readFigureData(entry);
        break;

      case 2: // Square
        f = Square.readFigureData(entry);
        break;

      case 3: // see figure
        if (f != null) {
          f.drawTxt();
          f.printDescription();
        } else {
          System.out.println("Error: you must select a figure first");
        }
        break;

      default: // error
        System.out.println("Wrong option");

      }

    } while (iOption != 0); // Repeats till user select option 0-exit

  }

}
	  

Exercise Section2.3. Cifrado

Objetivo

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.

Ejercicio

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.

Solución

La solución con la clase abstracta (herencia) se puede ver en el siguiente listado:

public abstract class Cypher {

  // Functions that define the encryption and decryption.
  abstract public String encrypt(String string);

  abstract public String decrypt(String string);

  /**
   * 
   * @param args
   *          [0] encrypt/decrypt
   * @param args
   *          [1] string to encrypt/decrypt
   */
  public static void main(String[] args) {
    if (args.length != 2) {
      System.out.println("Usage: java Cypher encrypt/decrypt [string]");
    } else {
      Cypher cc = new CaesarCypher();
      if (args[0].equals("encrypt")) {
        System.out.println(cc.encrypt(args[1]));
      } else if (args[0].equals("decrypt")) {
        System.out.println(cc.decrypt(args[1]));
      } else {
        System.out.println("Usage: java Cypher encrypt/decrypt [string]");
      }
    }
  } // main

} // Cypher

/**
 * Specific implementation for a cypher, which replace each plain-text letter
 * with a fixed number of places down the alphabet.
 * Simple version ignoring punctuation marks.
 */
class CaesarCypher extends Cypher {
  /** Number of places which are going to be used for the replacement */
  private static int CHARS_NUMBER_TO_SHIFT = 10;

  /**
   * Encrypts a string with the basic Caesar's cypher.
   * 
   * @param string
   *          String to encrypt
   * @return Encrypted String
   */
  public String encrypt(String string) {
      String encryptedString = "";
      for (int i = 0; i < string.length(); i++) {
	  char letter = string.charAt(i);
	  letter = (char) (letter + CHARS_NUMBER_TO_SHIFT);
	  if (letter > 'z') {
	      letter = (char) (letter - 'z' + 'a' -1);
	  }
	  
	  encryptedString += letter;
      }
      return encryptedString;
    
  } // encrypt

  /**
   * Decrypts a string with the basic Caesar's cypher.
   * 
   * @param string
   *          String to decrypt
   * @return Decrypted String
   */
   public String decrypt(String string) {
       String decryptedString = "";
       for (int i = 0; i < string.length(); i++) {
	   char letter = string.charAt(i);
	   letter = (char) (letter - CHARS_NUMBER_TO_SHIFT);
	   if (letter < 'a') {
	       letter = (char) (letter + 'z' - 'a' + 1);
	   }
	   
	   decryptedString += letter;
       }
       return decryptedString;
   } // decrypt
    

} // CaesarCypher
	  

La solución con la interfaz se puede ver en el siguiente listado:

public class Cypher {
  /**
   * 
   * @param args
   *          [0] encrypt/decrypt
   * @param args
   *          [1] string to encrypt/decrypt
   */
  public static void main(String[] args) {
    if (args.length != 2)
      System.out.println("Usage: java Cypher encrypt/decript [string]");
    else {
      Cryptable cc = new CaesarCypher();

      if (args[0].equals("encrypt")) {
        System.out.println(cc.encrypt(args[1]));
      } else if (args[0].equals("decrypt")) {
        System.out.println(cc.decrypt(args[1]));
      } else {
        System.out.println("Usage: java Cypher encrypt/decript [string]");
      }
    }
  } // main
} // Cifrado

// Set of methods that specify the possibility of encrypt and decrypt in the
// class that implements it.
interface Cryptable {
  public String encrypt(String string);

  public String decrypt(String string);
}

/**
 * Implementation for a cypher, which replace each plain-text letter with a
 * fixed number of places down the alphabet
 */
class CaesarCypher implements Cryptable {
  /** Number of places which are going to be used for the replacement */
  private static int CHARS_NUMBER_TO_SHIFT = 10;

  /**
   * Characters that can be used for the shift The characters ordering may be
   * changed in the array
   */
  private static char characters[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
      'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
      'w', 'x', 'y', 'z', '_', '-', '.', '#', '@', '&', '%', '/', '(', '(',
      '+', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', '0', '1',
      '2', '3', '4', '5', '6', '7', '8', '9', 'L', 'M', 'N', 'O', 'P', 'Q',
      'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z' };

  /**
   * Given a character, this function returns its position in the array if it
   * exists, -1 otherwise
   * 
   * @param character
   * @return
   */
  private int searchCharPosition(char character) {
    int pos = -1;
    boolean found = false;

    int i = 0;
    while ((!found) && (i < characters.length)) {
      found = (characters[i] == character);
      i++;
    }
    if (found) {
      return (i - 1);
    } else {
      return pos;
    }
  } // searchCharPosition()

  /**
   * Replace the given character for the encryption.
   * 
   * @param character
   *          Char to replace
   * @return New (already replaced) character, which was CHARS_NUMBER_TO_SHIFT
   *         positions down the alphabet
   */
  private char encryptReplace(char character) {
    int pos = searchCharPosition(character);
    if (pos == -1) {
      return character;
    } else {
      int replacingCharPosition = 0;
      int newPosition = pos + CaesarCypher.CHARS_NUMBER_TO_SHIFT;
      if (newPosition <= characters.length - 1) {
        replacingCharPosition = newPosition;
      } else {
        replacingCharPosition = newPosition - characters.length;
      }

      return characters[replacingCharPosition];
    }

  } // encryptReplace()

  /**
   * Replace the given character for the decryption.
   * 
   * @param character
   *          Char to replace
   * @return New (already replaced) character, which was CHARS_NUMBER_TO_SHIFT
   *         positions up the alphabet
   */
  private char decryptReplace(char character) {
    int pos = searchCharPosition(character);
    if (pos == -1) {
      return character;
    } else {
      int replacingCharPosition = 0;
      int newPosition = pos - CaesarCypher.CHARS_NUMBER_TO_SHIFT;
      if (newPosition >= 0) {
        replacingCharPosition = newPosition;
      } else {
        replacingCharPosition = characters.length - Math.abs(newPosition);
      }
      return characters[replacingCharPosition];
    }
  } // decryptReplace()

  /**
   * Encrypts a string with the basic Caesar's cypher.
   * 
   * @param string
   *          String to encrypt
   * @return Encrypted String
   */
  public String encrypt(String string) {
    String encryptedString = "";
    for (int i = 0; i < string.length(); i++) {
      char letter = string.charAt(i);
      letter = encryptReplace(letter);
      encryptedString += letter;
    }
    return encryptedString;
  } // encrypt

  /**
   * Encrypts a string with the basic Caesar's cypher.
   * 
   * @param string
   *          String to decrypt
   * @return Decrypted String
   */
  public String decrypt(String string) {
    String decryptedString = "";
    for (int i = 0; i < string.length(); i++) {
      char letter = string.charAt(i);
      letter = decryptReplace(letter);
      decryptedString += letter;
    }
    return decryptedString;
  } // decrypt
} // CifradoCesar