Herencia y polimorfismo
Entender la herencia
El mecanismo de la herencia se utiliza mucho en POO, por lo que es importante recordar su utilidad.
Heredar de una clase ayuda a «especializar» determinados comportamientos y algunas de sus propiedades, aprovechando sus servicios básicos y, de esta manera, evitando cualquier redundancia de código.
Java y C# solo permiten una única herencia por nivel (al contrario que C++), pero es posible heredar de una clase en sí misma ya heredada y así sucesivamente, para formar una jerarquía de clases que partan de la más global hasta la más detallada.
Ejemplo de jerarquía de clases
Por defecto, una clase puede servir de «padre» a varias clases. Sin ninguna duda, el mejor ejemplo es java.lang.Object, que es la clase raíz de todos los tipos -y por lo tanto, de todas las clases- de Java. Recordemos que esta herencia está implícita y no necesita ninguna definición particular.
Codificación de la superclase (clase de base) y de su subclase (clase heredada)
El código de una clase define las reglas de su eventual herencia.
1. Prohibir la herencia
En primer lugar ¿es deseable hacer una clase «heredable»? Si el análisis demuestra que no, entonces se debe utilizar la palabra clave final en la definición de la clase, para prohibir cualquier herencia.
Sintaxis de declaración de una clase «final»
[visibilidad] final class NombreClase
{
//...
}
Ejemplo de clase «final»
public final class Director {
//...
}
Como se muestra en la siguiente captura, IntelliJ IDEA rechaza compilar una clase que extiende de la clase final Director:
La clase Java String es una clase de tipo «final».
Una clase que no contiene la palabra clave final en su definición se considera extensible.
2. Definir los miembros heredables
Una superclase elige a sus miembros «transmisibles», gracias a sus atributos de accesibilidad. De esta manera, las clases heredadas (subclases) tendrán el permiso de utilizar y redefinir los miembros de tipo protected y, por supuesto, los miembros de tipo public. Respecto a los miembros de tipo private de la superclase, permanecerán inaccesibles para sus subclases.
3. Sintaxis...
Comunicación entre clase de base y clase heredada
1. Los constructores
Cuando una clase heredada se instancia, el constructor de su superclase se llama antes que el suyo. A continuación se muestra un extracto de código, seguido del resultado en la consola que lo atestigua.
package com.eni;
// El punto de entrada de nuestro ejemplo
public class ClasePrincipal {
public static void main(String[] args) {
DemoHerencia dh = new DemoHerencia();
dh.Prueba();
}
}
package com.eni;
// La clase Demoherencia va...
public class DemoHerencia {
// a través de su método Prueba...
public void Prueba(){
//... a mostrar la instanciación de la heredada
System.out.println("Instanciación de una ClaseHija");
ClaseHija claseHija = new ClaseHija();
//
}
}
package com.eni;
// Definición de una clase heredada
// de la superclase ClaseMadre
public class ClaseHija extends ClaseMadre {
// con las propiedades public, protected y private
public String PublicPropClaseHija;
protected String ProtectedPropClaseHija;
private String PrivatePropClaseHija;
public ClaseHija() {
System.out.println("Contd Clase Hija");
}
}
package com.eni;
// Definición de la "superclase".
public class ClaseMadre {
// con las propiedades public, protected y private
public String PublicPropClaseMadre;
protected String ProtectedPropClaseMadre;
private String PrivatePropClaseMadre;
public ClaseMadre() { ...
Ejercicio
1. Enunciado
Cree una nueva solución de tipo consola.
Añada una clase CuentaBancaria para representar una cuenta bancaria, con las siguientes propiedades:
-
Titular (String).
-
Número (Integer que contiene un valor único asignado a la instanciación; el primer número de cuenta será 100).
-
Saldo (double).
Y los siguientes métodos:
-
Ingresar (permite ingresar dinero en la cuenta).
-
Retirar (permite retirar dinero de la cuenta).
-
Consultar (muestra toda la información de la cuenta).
Añada una clase CuentaBancariaRemunerada que represente a una cuenta bancaria remunerada que hereda de la clase creada con anterioridad, para la que el constructor recibirá como argumentos el nombre del titular y el porcentaje de remuneración de la cuenta.
Redefina el método Ingresar de CuentaBancariaRemunerada para que la cantidad ingresada se aumente con el porcentaje de remuneración definido en el constructor. No soñemos; este funcionamiento bancario atípico se hace únicamente para simplificar el ejercicio.
Codifique en el main una secuencia que permita comprobar el funcionamiento de las clases.
2. Corrección
package labcuentabancaria;
public class CuentaBancaria {
// Entero de tipo static que contiene
// el contador global de número de cuenta
private static Integer numCont = 100;
// Nombre del titular
private String Titular;
public String getTitular() {
return Titular;
}
public final void setTitular(String Titular) {
this.Titular = Titular;
}
// Número...
Las clases abstractas
Puede suceder que una superclase contenga métodos imposibles de implementar porque no tengan ningún sentido sin un mínimo de especialización.
Por ejemplo, una clase de base FormaGeometrica ofrece un método virtual Diseñar. A continuación, esta clase se extiende con las clases Triangulo, Rectangulo y Circulo. Cada una va a sustituir e implementar su propio método Diseñar.
Por lo tanto, la implementación del método Diseñar en la clase de base FormaGeometrica no tiene ningún sentido, porque cada forma es específica y a nivel de FormaGeometrica esta forma es abstracta.
En este caso, ¿por qué definir el método Diseñar en FormaGeometrica?
Por supuesto, esto está relacionado con el polimorfismo. Imagine que está construyendo una aplicación de diseño y esta aplicación administra una serie de formas geométricas cuidadosamente definidas y guardadas por el usuario. Puede administrar una lista de objetos de tipo Triangulo, una lista de objetos de tipo Rectangulo, etc. Buena suerte, porque esto pronto se convertirá en algo bastante pesado. Construyendo una lista de objetos de tipo FormaGeometrica, puede rellenarla de objetos de tipo Triangulo, Rectangulo y Circulo. ¿Para qué? Porque los tres extienden la superclase FormaGeometrica y cualquier clase derivada puede implícitamente...
El polimorfismo
1. Entender el polimorfismo
En programación orientada a objetos, el polimorfismo permite a una clase heredada presentarse en una operación como su clase de base o como una de sus interfaces. Gracias a la virtualización de los métodos, la operación que llama al método de base se «enruta» en la clase heredada. De esta manera, se obtiene una «especialización» de la operación.
Cualquier clase derivada puede implícitamente convertirse en un objeto de su clase de base.
En lenguaje Java, la virtualización, por defecto automática, se ha detallado en las páginas anteriores. Por un lado, permite a la clase de base definir qué métodos se pueden especializar por sus heredadas y, por otra, a su heredada o sus heredadas especializar los métodos.
En lenguaje Java, cada objeto es «polimorfo». Se puede considerar como su propio tipo o como el de su clase de base y, por saltos sucesivos, como el tipo raíz de todos los tipos, es decir, java.lang.Object.
2. Explotación del polimorfismo
La mejor programación es aquella que incentiva la menor dependencia entre los módulos. Una arquitectura compuesta de loosely coupled modules (módulos débilmente acoplados) es muy apreciada porque permite la evolución de las capas independientemente del funcionamiento global. Lo que ayer aparecía como...