Programación orientada a objetos: herencia

Programación orientada a objetos: herencia

Siguiendo nuestro curso de programación, en este artículo vamos a explicar una breve introducción a la herencia. En ocasiones cuando desarrollamos una aplicación nos surge la necesidad de tener tipos definidos por nosotros que tengan muchas cosas en común, por ejemplo, en nuestra granja un ejemplo lo tenemos en los animales, quizás nuestra aplicación tenga la necesidad de especificar un subtipo de la clase Animal, por ejemplo un tipo Cerdo y un tipo Vaca.

herencia_simple

Estos dos tipos tendrán en gran medida unos atributos y métodos comunes, ya que ambas son del tipo Animal. Por ejemplo cualquier animal come, es una funcionalidad común que será implementada en un método comer. Nosotros si queremos dos nuevos tipos Vaca y Cerdo podemos definir dos clases nuevas Vaca y Cerdo que nos sirvan para instanciar objetos de estos tipos:

public class Cerdo{}public class Vaca{}

De esta forma hemos definido dos nuevos tipos, el tipo Vaca y el tipo Cerdo, pero hay un problema. Si por ejemplo ambas clases disponen de un atributo común "nombre" deberemos definirlo en ambas y así con todo. Otro ejemplo, tenemos un método de 200 lineas que solo cambia en una, tendremos que copiarlo en ambas clases. Nuestro mayor problema es que estamos repitiendo código. Por tanto tenemos los siguientes problemas por duplicar código:

  • Hace más difícil el mantenimiento. Se incrementa el trabajo. Cuando se modifique algo en una casi seguro que lo tendremos que modificar en la otra.
  • Existe riesgo de errores a través del mantenimiento incorrecto. Es muy probable que cuando modifiquemos algo lo hagamos mal.

Java al igual que la gran mayoría de los lenguajes orientados a objetos traen una herramienta para no tener que repetir código, esta herramienta o también denominada técnica es la herencia.

La herencia en Java

La herencia en Java nos permite extraer una superclase que refactorizando, nos resuelve nuestro problema de la duplicación de código. Es decir, en Java la herencia es una técnica para reutilizar código, nuestra idea es tener una clase padre o superclase que contenga ese código que será duplicado y que luego las clases hijas o subclases hereden de esa superclase y no tengan que duplicar el código y extiendan su funcionalidad. Las subclases heredarán todos los campos (atributos y métodos) pero no los tendrán replicados en su código. Vamos a llevarlo todo a nuestra aplicación.

Con la clase Vaca:

public class Vaca extends Animal{public Vaca(String nombre) {    super(nombre);}}

Si nos fijamos lo único que tenemos que hacer es definir la clase y en la cabecera de la clase añadir la palabra reservada extends y el nombre de la clase de la que estamos heredando. También debemos definir un constructor que para los parámetros que ya estén en el constructor de la superclase los pasamos por parámetro a un método super(...). Esto ocurre porque hemos definido un constructor en la superclase Animal y por tanto debemos refactorizarlo en nuestras subclases. Vamos a verlo con detalle para la clase Cerdo:

Definimos la clase Cerdo:

public class Cerdo extends Animal{}

Vemos como Eclipse nos avisa de un error:

problema_cerdo_noheredabien

Vamos sobre la linea y aceptamos la solución que Eclipse nos recomienda:

problema_cerdo_solucion

Ya tenemos las clases, ahora vamos a probar como funcionan.

Vamos a nuestra clase Lanzador y copiamos el siguiente código:

public static void main(String[] args) {    Granja g = new Granja("granja1");    Cerdo cerdo = new Cerdo("Cerdo Manolo");    Vaca vaca = new Vaca("Vaca Marisa");    g.añadirAnimal(cerdo);    g.añadirAnimal(vaca);    System.out.println("Vemos los animales que tenemos en la granja");    g.muestraAnimales();}

Si observamos la salida:

Vemos los animales que tenemos en la granjaCerdo Manolo con peso 0Vaca Marisa con peso 0

Vemos que podemos añadir objetos de tipo Cerdo y tipo Vaca porque extienden la funcionalidad de la clase Animal, es decir, heredan de esta clase. Entonces podemos concluir, aunque no sea del todo exacto que, un tipo Animal podrá ser un tipo Vaca o tipo Cerdo. Podemos resumirlo todo en:

  • Los objetos de las subclases pueden ser asignados a las variables de un tipo superclase.
  • Se pueden usar objetos de subtipos en cualquier lugar en el que se espera un objeto de supertipo.

Profundizando

Vamos a añadir el siguiente método interno a nuestra clase Animal:

private void imprimeNombre(){    System.out.println("El nombre del animal es: "+nombre);}

Vamos a modificar nuestro lanzador con las siguientes lineas:

cerdo.imprimeNombre();vaca.imprimeNombre();

Vemos que no podemos, nos muestra el siguiente mensaje:problema_herencia_metodoPrivado

Si nos fijamos nos dice que "The method imprimeNombre() from the type Animal is not visible Lanzador.java" es decir, que no podemos acceder a el porque es privado, tenemos entonces que darnos cuenta que:

  • Los miembros definidos como públicos (en la superclase o subclase) serán accesibles para los objetos de otras clases.
  • Los miembros definidos como privados (en la superclase o subclase) serán inaccesibles para los objetos de otras clases.
  • Una subclase no puede acceder a los miembros privados de su superclase.
  • Una subclase puede invocar a cualquier método público de su superclase.

Por tanto, si recordamos los niveles y queremos que sea accesible lo tendremos que cambiar a public o protected. Es habitual poner todos los métodos como protected aunque otra tendencia es solamente fijar los getters y setters como protected y los demás como public. No es algo de lo que nos tengamos que preocupar en exceso. Nosotros vamos a ponerlos todo como protected:

public class Animal {private String nombre;private int edad;private double peso;public Animal(String nombre) {    this.nombre = nombre;}protected int getEdad() {    return edad;}protected void setEdad(int edad) {    this.edad = edad;}protected String getNombre() {    return nombre;}protected void setNombre(String nombre) {    this.nombre = nombre;}protected double getPeso() {    return peso;}protected void setPeso(double peso) {    this.peso = peso;}protected void imprimeNombre(){    System.out.println("El nombre del animal es: "+nombre);}   }

Para concluir tenemos que darnos cuenta que la herencia no es una técnica nada sencilla de implementar, y que su mal uso puede causar un mal diseño en el software y siempre tenemos que tener claras las consecuencias de usar la herencia. También tenemos que darnos cuenta que la herencia se usa para no duplicar código, pero para usarla es también condición necesaria extender la funcionalidad de esa clase para asegurar tener un buen diseño.

Como en otras ocasiones, podéis descargar el código fuente para realizar las pruebas.

Para ti
Queremos saber tu opinión. ¡Comenta!