jueves, 13 de septiembre de 2012

Herencia y polimorfismo

Herencia

La herencia es el concepto que permite que se puedan definir nuevas clases basadas en clases existentes, con el fin de re-utilizar código previamente desarrollado generando una jerarquía de clases dentro de la aplicación. Entonces, si una clase se deriva de otra, esta hereda sus atributos y métodos. La clase derivada puede añadir nuevos atributos y métodos y/o redefinir los atributos y métodos heredados. Para que un atributo y método puedan ser heredados, es necesario que su visibilidad sea “protected”.

En Java, a diferencia de otros lenguajes orientados a objetos, una clase sólo puede derivar de una única clase, con lo cual no es posible realizar herencia múltiple con base en clases. Sin embargo, es posible “simular” la herencia múltiple con base en las interfaces.

Un ejemplo del concepto de herencia puede ser considerando los miembros de una institución de educación. La institución está conformada por personas, pero cada persona tiene un rol dentro de la institución, que podría ser: empleado, estudiante, egresado. Así mismo, de empleado se podría derivar la clasificación académico, administrativo. De académico se puede derivar, decano, coordinador, docente. De administrativo se puede derivar, de acuerdo a la cantidad de departamentos de la institución.

La representación de herencia del caso anteriormente expuesto en lenguaje de modelado es la siguiente.

Otro ejemplo del concepto de herencia es el de figuras geométricas. Se puede considerar una clase denominada FiguraGeometrica, del cual heredan las clases Cuadrado, Circulo, Triangulo y Rectángulo.

En este caso, la clase FiguraGeometrica, poseería un atributo que puede ser llamado valor1. Este atributo es heredado por las clases Cuadrado, Circulo, Triangulo y Rectángulo. Sin embargo, las clases Rectángulo y Triangulo requieren dos valores. Esto indica que deben incluirse como atributos de cada una de estas clases.

Por otro lado, la clase FiguraGeometrica puede implementar los métodos consultores y modificadores, los que podrán ser usados por cada una de las clases que la heredan.

Así mismo, de la clase Cuadrado, es posible heredar la clase Cubo. De la clase Triangulo es posible heredar la clase Pirámide y Cono. De la clase Circulo es posible heredar la clase Esfera y Cilindro.

La representación de herencia del caso anteriormente expuesto en lenguaje de modelado es la siguiente.

Sentencia Extends

La sentencia “extends” permite implementar el concepto de herencia. Se incluye para que una clase herede de otra clase. Por ejemplo, en el caso de jerarquía de herencia de personal académico, debe existir una clase persona y una clase estudiante. Al implementar la clase estudiante, se le debe incluir la sentencia extends para que herede de la clase persona. La sintaxis es la siguiente.

public class Persona{
 ...
}

public class Estudiante extends Persona{
 ...
}

Una vez incluida la sentencia extends, la clase estudiante tiene acceso a atributos y métodos protegidos de la clase persona.

Sentencia super

La sentencia “super” es utilizada para acceder a métodos implementados en la clase superior en el concepto de herencia. Esta sentencia es comúnmente utilizada para acceder al constructor de la clase superior desde el constructor de la clase inferior. Por ejemplo en el caso de jerarquía de herencia de personal académico, debe existir una clase persona con atributos como identificación, nombre, apellido y correo, y una clase estudiante que puede acceder a estos atributos pero que adicionalmente tiene atributos como código y facultad. Al implementar el constructor de la clase estudiante para asignar los valores de los atributos, se puede hacer un llamado al constructor de la clase persona enviándole los parámetros definidos en dicha clase. La sintaxis es la siguiente.

public class Persona{
 
 protected int id
 protected String nombre
 protected String apellido
 protected String correo

 public Persona(int id, String nombre, String apellido, String correo){
  this.id=id;
  this.nombre=nombre;
  this.apellido=apellido;
  this.correo=correo;
 }
}
public class Estudiante extends Persona{
 
 private int codigo
 private String facultad

 public Estudiante(int id, String nombre, String apellido, String correo, int codigo, String facultad){
  super(id, nombre, apellido, correo);  
  this.codigo=codigo;
  this.facultad=facultad;
 }
}

En el ejemplo anterior, el constructor de la clase estudiante, hace un llamado al constructor de la clase persona asignando los valores a los atributos allí definidos.

Sobre-escritura de métodos

La sobre-escritura de métodos es una característica que se presenta en el concepto de herencia que consiste en implementar un método en la clase superior e inferior en la jerarquía de herencia. Por ejemplo, considerando las clases cuadrado y cubo de la jerarquía de Figuras Geométricas que se presenta en la siguiente figura, es posible crear un método getArea tanto para la clase cuadrado y para la clase cubo. Entonces, si se crea una referencia de la clase cuadrado, dependiendo de la instancia del objeto que se crea que puede ser de cuadrado o cubo, se accede al método implementado en cuadrado o en cubo respectivamente.

La implementación de esta jerarquía es la siguiente

Clase Cuadrado
package FigurasGeometricas;

public class Cuadrado {
 protected int valor1;

 public Cuadrado(double valor1) {
  this.valor1=valor1;
 }

 public double getArea() {
  return Math.pow(this.valor1, 2);
 }
}
Clase Cubo
package FigurasGeometricas;

public class Cubo extends Cuadrado {

 public Cubo(double valor1) {
  super(valor1);
 }

 public double getArea() {
  return Math.pow(this.valor1, 3);
 } 
}

En la implementación anterior, sobre escribe el método getArea, debido a que el área del cuadrado es diferente al área del cubo. Java identifica a cual método sobre-escrito debe acceder en tiempo de ejecución.

Clases Abstractas

Una clase abstracta es aquella que no puede ser instanciada, es decir, no se pueden crear objetos de esta clase. Se usa para permitir que otras clases hereden de esta proporcionando atributos y métodos que son comunes de las clases heredadas. La sintaxis para la creación de una clase abstracta es la siguiente

public abstract class FiguraGeometrica {
 ...
}

Una clase abstracta puede contener atributos y métodos. Sin embargo, adicionalmente, puede contener métodos abstractos, los cuales son definidos pero no implementados. Su finalidad es que las clases que heredan de la clase abstracta, implementen de forma obligatoria dichos métodos abstractos.

Con base en la jerarquía de herencia de Figuras Geométricas, la implementación de la clase FiguraGeometrica es la siguiente

package FigurasGeometricas;

public abstract class FiguraGeometrica {

 protected double valor1;

 public FiguraGeometrica(double valor1) {
  super();
  this.valor1 = valor1;
 }

 public double getValor1() {
  return valor1;
 }

 public void setValor1(double valor1) {
  this.valor1 = valor1;
 }
 
 public abstract double getArea();
 public abstract double getPerimetro();
}

De esta forma, las clases que hereden de la clase FiguraGeometrica, pueden acceder al atributo valor1, a los métodos getValor1 y setValor1 y deben implementar los métodos getArea y getPerimetro.

Cuando se implementa una clase abstracta, es importante tener en cuenta que la clase no debe ser instanciada. Por ejemplo en el caso de FiguraGeometrica, no se debe crear un objeto instancia de esta clase, debido a que FiguraGeometrica en la realidad actúa como una generalización, es decir, para realizar cálculos sobre una figura, es necesario determinar a que figura se hace referencia como por ejemplo, un cuadrado o un triangulo. Por otro lado, al definir un método abstracto, es necesario identificar que servicios deben obligatoriamente implementar las clases que hereden de la clase abstracta. Por ejemplo, cualquier figura como cuadrado o triangulo tienen área y tienen perímetro. Por consiguiente estos servicios deben ser implementados mediante la definición de un método abstracto.

De esta manera la implementación de las clases que heredan de FiguraGeometrica es la siguiente.

Clase Cuadrado
package FigurasGeometricas;

public class Cuadrado extends FiguraGeometrica {

 public Cuadrado(double valor1) {
  super(valor1);
 }

 @Override
 public double getArea() {
  return Math.pow(this.valor1, 2);
 }

 @Override
 public double getPerimetro() {
  return this.valor1*4;
 }
}
Clase Circulo
package FigurasGeometricas;

public class Circulo extends FiguraGeometrica {

 public Circulo(double valor1) {
  super(valor1);
 }

 @Override
 public double getArea() {
  return Math.PI*Math.pow(this.valor1, 2);
 }

 @Override
 public double getPerimetro() {
  return Math.PI*this.valor1;
 }
}
Clase Triangulo
package FigurasGeometricas;

public class Triangulo extends FiguraGeometrica {
 private double valor2;
 
 public Triangulo(double valor1, double valor2) {
  super(valor1);
  this.valor2 = valor2;
 }

 public double getValor2() {
  return valor2;
 }

 public void setValor2(double valor2) {
  this.valor2 = valor2;
 }

 @Override
 public double getArea() {
  return (this.valor1*this.valor2)/2;
 }

 @Override
 public double getPerimetro() {
  return this.valor1 + (2 * Math.sqrt((Math.pow(this.valor1, 2)+Math.pow(this.valor2, 2))));
 }
}
Clase Rectangulo
package FigurasGeometricas;

public class Rectangulo extends FiguraGeometrica {
 private double valor2;

 public Rectangulo(double valor1, double valor2) {
  super(valor1);
  this.valor2 = valor2;
 }

 public double getValor2() {
  return valor2;
 }

 public void setValor2(double valor2) {
  this.valor2 = valor2;
 }

 @Override
 public double getArea() {
  return this.valor1*this.valor2;
 }

 @Override
 public double getPerimetro() {
  return 2*this.valor1 + 2*this.valor2;
 }
}
Interfaces

Una interface es un tipo especial de clase que permite realizar un conjunto de declaraciones de métodos sin implementación. En una interface también se pueden definir constantes, que son implícitamente public, static y final y deben siempre inicializarse en la declaración.

Para que una clase use las definiciones de una interface, dicha clase debe incluir la sentencia “implements” el cual indica que implementa la interface. La sintaxis es la siguiente.

public interface MiInterface {
 ...
}

public class MiClase implements MiInterface {
 ...
}

El objetivo de los métodos declarados en una interface es definir un tipo de conducta para las clases que implementan dicha interface. Todas las clases que implementan una determinada interface están obligadas a proporcionar una implementación de los métodos declarados en la interface, adquiriendo un comportamiento.

Una clase puede implementar una o varias interfaces. Para indicar que una clase implementa más de una interface se ponen los nombres de las interfaces, separados por comas, posterior a incluir la sentencia “implements”. La sintaxis es la siguiente

public class MiClase implements MiInterface1, MiInterface2, MiInterfaceN  {
 ...
}

Un ejemplo del uso de interfaces con base en el modelo de herencia de figuras geométricas es el siguiente. En este ejemplo, se plantea la posibilidad de dibujar una figura, como circulo o rectángulo. Para ello, se crea una interface denominada FiguraDibujable que contiene métodos que definen comportamiento de dibujo de la figura. Entonces se puede hacer una clase que herede de Circulo e implemente FiguraDibujable y una clase que herede de Rectangulo e implemente FiguraDibujable.

Clase FiguraGeometrica
package FigurasGeometricas;

public abstract class FiguraGeometrica {

 protected double valor1;

 public FiguraGeometrica(double valor1) {
  super();
  this.valor1 = valor1;
 }

 public double getValor1() {
  return valor1;
 }

 public void setValor1(double valor1) {
  this.valor1 = valor1;
 }
 
 public abstract double getArea();
 public abstract double getPerimetro();
}
Interface FiguraDibujable
package FigurasGeometricas;

import java.awt.Graphics;

public interface FiguraDibujable {
 public void setCoordenadas(int x, int y);
 public void dibujar2D(Graphics g);
}
Clase Circulo
package FigurasGeometricas;

public class Circulo extends FiguraGeometrica {

 public Circulo(double valor1) {
  super(valor1);
 }

 @Override
 public double getArea() {
  return Math.PI*Math.pow(this.valor1, 2);
 }

 @Override
 public double getPerimetro() {
  return Math.PI*this.valor1;
 }
}
Clase CirculoDibujable
Clase CirculoDibujable
package FigurasGeometricas;
import java.awt.Graphics;

public class CirculoDibujable extends Circulo implements FiguraDibujable {
 private int x;
 private int y;
 
 public CirculoDibujable(double valor1, int x, int y) {
  super(valor1);
  this.x = x;
  this.y = y;
 }

 @Override
 public void setCoordenadas(int x, int y) {
  this.x=x;
  this.y=y;
 }

 @Override
 public void dibujar2D(Graphics g) {
  g.drawOval(this.x, this.y, (int)this.valor1, (int)this.valor1);  
 }
}
Clase Rectangulo
package FigurasGeometricas;

public class Rectangulo extends FiguraGeometrica {
 protected double valor2;

 public Rectangulo(double valor1, double valor2) {
  super(valor1);
  this.valor2 = valor2;
 }

 public double getValor2() {
  return valor2;
 }

 public void setValor2(double valor2) {
  this.valor2 = valor2;
 }

 @Override
 public double getArea() {
  return this.valor1*this.valor2;
 }

 @Override
 public double getPerimetro() {
  return 2*this.valor1 + 2*this.valor2;
 }
}
Clase RectanguloDibujable
package FigurasGeometricas;

import java.awt.Graphics;

public class RectanguloDibujable extends Rectangulo implements FiguraDibujable {
 private int x;
 private int y;
 
 public RectanguloDibujable(double valor1, double valor2, int x, int y) {
  super(valor1, valor2);
  this.x = x;
  this.y = y;
 }

 @Override
 public void setCoordenadas(int x, int y) {
  this.x=x;
  this.y=y;
 }

 @Override
 public void dibujar2D(Graphics g) {
  g.drawRect(this.x, this.y, (int)this.valor1, (int)this.valor2);  
 }
}
POLIMORFISMO

El polimorfismo es la característica de la programación orientada a objetos que permite modificar la instancia de un objeto en tiempo de ejecución basado en una jerarquía de herencia. De esta forma, es posible generar una relación de vinculación denominada “binding”. El polimorfismo se puede realizar con clases superiores normales, abstractas e interfaces.

El objetivo del polimorfismo, consiste en poder acceder a diferentes servicios en tiempo de ejecución sin necesidad de implementar diferentes referencias a objetos. Esta característica, provee una gran flexibilidad en el proceso de desarrollo y ejecución de la aplicación.

Por ejemplo, considerando la jerarquía de herencia de Figuras Geométricas de la siguiente figura, es posible hacer uso del concepto de polimorfismo.

Con base en el modelo anterior, se puede crear una referencia de la clase FiguraGeometrica de la siguiente forma.

FiguraGeometrica figura;

A la referencia figura se le puede generar instancia de cualquiera de las clases que derivan de ella de la siguiente forma.

figura = new Circulo(5);
figura = new Cuadrado(5);
figura = new Rectangulo(5,2);
figura = new Triangulo(5,2);
figura = new Cubo(5);

En la primera línea, el objeto figura tiene la forma de circulo, de tal forma que si se accede al método getArea, se ejecuta el método implementado en círculo.

En la segunda línea, el objeto figura tiene la forma de cuadrado, de tal forma que si se accede al método getArea, se ejecuta el método implementado en cuadrado.

En la tercer línea, el objeto figura tiene la forma de rectángulo, de tal forma que si se accede al método getArea, se ejecuta el método implementado en rectángulo.

En la cuarta línea, el objeto figura tiene la forma de triangulo, de tal forma que si se accede al método getArea, se ejecuta el método implementado en triangulo.

En la quinta línea, el objeto figura tiene la forma de cubo, de tal forma que si se accede al método getArea, se ejecuta el método implementado en cubo.

La siguiente implementación del modelo presentado demuestra en tiempo de ejecución el concepto de polimorfismo.

Clase FiguraGeometrica
package FigurasGeometricas;

public abstract class FiguraGeometrica {

 protected double valor1;

 public FiguraGeometrica(double valor1) {
  super();
  this.valor1 = valor1;
 }

 public double getValor1() {
  return valor1;
 }

 public void setValor1(double valor1) {
  this.valor1 = valor1;
 }
 
 public abstract double getArea();
 public abstract double getPerimetro();
}
Clase Circulo
package FigurasGeometricas;

public class Circulo extends FiguraGeometrica {

 public Circulo(double valor1) {
  super(valor1);
 }

 @Override
 public double getArea() {
  return Math.PI*Math.pow(this.valor1, 2);
 }

 @Override
 public double getPerimetro() {
  return Math.PI*this.valor1;
 }
}
Clase Cuadrado
package FigurasGeometricas;

public class Cuadrado extends FiguraGeometrica {

 public Cuadrado(double valor1) {
  super(valor1);
 }

 @Override
 public double getArea() {
  return Math.pow(this.valor1, 2);
 }

 @Override
 public double getPerimetro() {
  return this.valor1*4;
 }
}
Clase Rectangulo
package FigurasGeometricas;

public class Rectangulo extends FiguraGeometrica {
 protected double valor2;

 public Rectangulo(double valor1, double valor2) {
  super(valor1);
  this.valor2 = valor2;
 }

 public double getValor2() {
  return valor2;
 }

 public void setValor2(double valor2) {
  this.valor2 = valor2;
 }

 @Override
 public double getArea() {
  return this.valor1*this.valor2;
 }

 @Override
 public double getPerimetro() {
  return 2*this.valor1 + 2*this.valor2;
 }
}
Clase Triangulo
package FigurasGeometricas;

public class Triangulo extends FiguraGeometrica {
 private double valor2;
 
 public Triangulo(double valor1, double valor2) {
  super(valor1);
  this.valor2 = valor2;
 }

 public double getValor2() {
  return valor2;
 }

 public void setValor2(double valor2) {
  this.valor2 = valor2;
 }

 @Override
 public double getArea() {
  return (this.valor1*this.valor2)/2;
 }

 @Override
 public double getPerimetro() {
  return this.valor1 + (2 * Math.sqrt((Math.pow(this.valor1, 2)+Math.pow(this.valor2, 2))));
 }
}
Clase Cubo
package FigurasGeometricas;

public class Cubo extends Cuadrado {

 public Cubo(double valor1) {
  super(valor1);
 }

 public double getArea() {
  return Math.pow(this.valor1, 3);
 } 
}
Clase Principal
package FigurasGeometricas;

public class Principal {

 public static void main(String[] args) {
  FiguraGeometrica figura;
  figura = new Circulo(5);
  System.out.println(figura.getClass());
  System.out.println("Area: "+figura.getArea());
  System.out.println("Perimetro: "+figura.getPerimetro());
  figura = new Cuadrado(5);
  System.out.println(figura.getClass());
  System.out.println("Area: "+figura.getArea());
  System.out.println("Perimetro: "+figura.getPerimetro());
  figura = new Rectangulo(5,2);
  System.out.println(figura.getClass());
  System.out.println("Area: "+figura.getArea());
  System.out.println("Perimetro: "+figura.getPerimetro());
  figura = new Triangulo(5,2);
  System.out.println(figura.getClass());
  System.out.println("Area: "+figura.getArea());
  System.out.println("Perimetro: "+figura.getPerimetro());
  figura = new Cubo(5);
  System.out.println(figura.getClass());
  System.out.println("Area: "+figura.getArea());
  System.out.println("Perimetro: "+figura.getPerimetro());
 }
}
Salida Estandar
class FigurasGeometricas.Circulo
Area: 78.53981633974483
Perimetro: 15.707963267948966
class FigurasGeometricas.Cuadrado
Area: 25.0
Perimetro: 20.0
class FigurasGeometricas.Rectangulo
Area: 10.0
Perimetro: 14.0
class FigurasGeometricas.Triangulo
Area: 5.0
Perimetro: 15.770329614269007
class FigurasGeometricas.Cubo
Area: 125.0
Perimetro: 20.0

En la implementación de la clase Principal, se puede apreciar que el objeto figura cambia de forma cada vez que se hace una nueva instancia. Así mismo en cada instancia accede a la implementación del método de la clase instanciada.

El último caso, realiza la instancia de la clase Cubo, en donde esta clase no tiene implementado el método getPerimetro. Para este caso, en el llamado a este método, se accede al método getPerimetro implementado en la clase superior que es Cuadrado.

1 comentario:

  1. Excelente ejemplo el de las figuras geométricas. Muy bien explicado y didáctico.

    ResponderEliminar