herencia-polimorfismo en java
DESCRIPTION
programación orientada a objetosTRANSCRIPT
Herencia en Java, con ejemplos
La Herencia es uno de los 4 pilares de la programación orientada a objetos
(POO) junto con laAbstracción, Encapsulación y Polimorfismo. Al principio cuesta
un poco entender estos conceptos característicos del paradigma de la POO porque
solemos venir de otro paradigma de programación como el paradigma de la
programación estructurada (ver la entrada”Paradigmas de Programación), pero se ha
de decir que la complejidad está en entender este nuevo paradigma y no en otra cosa.
En esta entrada vamos a explicar de la mejor manera posible que es la herencia y lo
vamos a explicar con un ejemplo.
Respecto a la herencia se han dado muchas definiciones como por ejemplo la
siguiente: “La herencia es un mecanismo que permite la definición de una clase a partir
de la definición de otra ya existente. La herencia permite compartir automáticamente
métodos y datos entre clases, subclases y objetos.“. Así de primeras esta definición es
un poco difícil de digerir para aquellos que estéis empezando con la POO, así que
vamos a intentar digerir esta definición con un ejemplo en el que veremos que la
herencia no es más que un “Copy-Paste Dinámico” o una forma de “sacar factor
común” al código que escribimos.
El ejemplo que proponemos es un caso en el que vamos a simular el
comportamiento que tendrían los diferentes integrantes de la selección española de
futbol; tanto los Futbolistas como el cuerpo técnico (Entrenadores, Masajistas, etc…).
Para simular este comportamiento vamos a definir tres clases que van a representaran
a objetos Futbolista, Entrenador y Masajista. De cada unos de ellos vamos a necesitar
algunos datos que reflejaremos en los atributos y una serie de acciones que
reflejaremos en sus métodos. Estos atributos y métodos los mostramos en el siguiente
diagrama de clases:
NOTA: en este diagrama y en adelante no vamos a poner los constructores y métodos getter y setter con el fin de que el diagrama nos quede grande e “intendible” aunque en un buen diagrama de clases deberían aparecer para respetar el principio de encapsulación de la POO
Como se puede observar, vemos que en las tres clases tenemos atributos y
métodos que con iguales ya que los tres tienen los atributos id, Nombre, Apellidos y
Edad; y los tres tienen los métodos de Viajar yConcentrarse:
A nivel de código tenemos lo siguiente tras ver el diagrama de clases:
public class Futbolista
{
private int id;
private String Nombre;
private String Apellidos;
private int Edad;
private int dorsal;
private String demarcacion;
// constructor, getter y setter
public void Concentrarse() {
...
}
public void Viajar() {
...
}
public void jugarPartido() {
public class Entrenador
{
private int id;
private String Nombre;
private String Apellidos;
private int Edad;
private String idFederacion;
// constructor, getter y setter
public void Concentrarse() {
...
}
public void Viajar() {
...
}
public void dirigirPartido() {
...
public class Masajista
{
private int id;
private String Nombre;
private String Apellidos;
private int Edad;
private String Titulacion;
private int aniosExperiencia;
// constructor, getter y setter
public void Concentrarse() {
...
}
public void Viajar() {
...
}
public void darMasaje() {
...
}
public void entrenar() {
...
}
}
}
public void dirigirEntreno() {
...
}
}
...
}
}
Lo que podemos ver en este punto es que estamos escribiendo mucho código
repetido ya que las tres clases tienen métodos y atributos comunes, de ahi y como
veremos enseguida, decimos que la herencia consiste en “sacar factor común” para
no escribir código de más, por tanto lo que haremos sera crearnos una clase con el
“código que es común a las tres clases” (a esta clase se le denomina en la
herencia como“Clase Padre o SuperClase”) y el código que es especifico de cada
clase, lo dejaremos en ella, siendodenominadas estas clases como “Clases
Hijas”, las cuales heredan de la clase padre todos los atributos y métodos
públicos o protegidos. Es muy importante decir que las clases hijas no van a heredar
nunca los atributos y métodos privados de la clase padre, así que mucho cuidado
con esto. En resumen para que veáis la ventaja de la herencia, tenemos ahora una
clase padre con ‘n’ lineas de código y tres clases hijas con ‘a’, ‘b’ y ‘c’ lineas de códigos
respectivamente, por tanto si hecháis cuentas, hemos reducido nuestro código en
‘2n’ líneas menos ya que antes teníamos ‘(n+a)+(n+b)+(n+c)’ líneas de código y
ahora tras aplicar herencia tenemos ‘n+a+b+c’ líneas, aunque también es cierto que
tenemos una clase más, pero veremos un poco más adelante la ventaja de tener esa
clase padre. En resumen, al “sacar factor común” y aplicar herencia, tenemos las
siguientes clases:
A nivel de código, las clases quedarían implementadas de la siguiente forma:
public class SeleccionFutbol
{
protected int id;
protected String Nombre;
protected String Apellidos;
protected int Edad;
// constructor, getter y setter
public void Concentrarse() {
...
}
public void Viajar() {
...
}
}
public class Futbolista extends SeleccionFutbol
{
private int dorsal;
private String demarcacion;
public Futbolista() {
super();
}
public class Entrenador extends SeleccionFutbol
{
private String idFederacion;
public Entrenador() {
super();
}
public class Masajista extends SeleccionFutbol
{
private String Titulacion;
private int aniosExperiencia;
public Masajista() {
super();
}
// getter y setter
public void jugarPartido() {
...
}
public void entrenar() {
...
}
}
// getter y setter
public void dirigirPartido() {
...
}
public void dirigirEntreno() {
...
}
}
// getter y setter
public void darMasaje() {
...
}
}
Como podéis observar ahora queda un código mucho más limpio,
estructurado y con menos líneas de código, lo que lo hace más legible, cosa que
es muy importante y lo que todavía lo hace más importante es que es un código
reutilizable, lo que significa que ahora si queremos añadir más clases a nuestra
aplicación como por ejemplo una clase Médico, Utiller@, Jefe/a de prensa etc. que
pertenezcan también al equipo técnico de la selección Española, lo podemos hacer de
forma muy sencilla ya que en la clase padre (SeleccionFutbol) tenemos implementado
parte de sus datos y de su comportamiento y solo habrá que implementar los atributos
y métodos propios de esa clase. ¿Empezáis a ver la utilidad de la herencia?.
Ahora si os habéis fijado bien en el código que se ha escrito y sino habéis tenido
experiencia con la herencia en Java, habréis podido observar dos palabras reservadas
“nuevas” como son “extends“, “protected” y “super“. Pues bien, ahora vamos a
explicar el significado de ellas:
extends: Esta palabra reservada, indica a la clase hija cual va a ser su clase
padre, es decir que por ejemplo en la clase Futbolista al poner “public class
Futbolista extends SeleccionFutbol” le estamos indicando a la clase ‘Futbolista’
que su clase padre es la clase ‘SeleccionFutbol’ o dicho de otra manera para
que se entienda mejor, al poner esto estamos haciendo un “copy-paste
dinámico” diciendo a la clase ‘Futbolista’ que se ‘copie’ todos los atributos y
métodos públicos o protegidos de la clase ‘SeleccionFutbol’. De aquí viene esa
‘definición’ que dimos de que la herencia en un ‘copy-paste dinámico’.
protected: sirve para indicar un tipo de visibilidad de los atributos y métodos de
la clase padre y significa que cuando un atributo es ‘protected’ o protegido, solo
es visible ese atributo o método desde una de las clases hijas y no desde otra
clase.
super: sirve para llamar al constructor de la clase padre. Quizás en el código
que hemos puesto no se ha visto muy bien, pero a continuación lo mostramos
de formas más clara, viendo el constructor de los objetos pasándole los
atributos:
public class SeleccionFutbol {
......
public SeleccionFutbol() {
}
public SeleccionFutbol(int id, String nombre, String apellidos, int edad) {
this.id = id;
this.Nombre = nombre;
this.Apellidos = apellidos;
this.Edad = edad;
}
......
public class Futbolista extends SeleccionFutbol {
......
public Futbolista() {
super();
}
public Futbolista(int id, String nombre, String apellidos, int edad, int dorsal, String demarcacion) {
super(id, nombre, apellidos, edad);
this.dorsal = dorsal;
this.demarcacion = demarcacion;
}
......
Hasta aquí todo correcto, pero ahora vamos a ver como trabajamos con estas
clases. Para ver este funcionamiento de forma clara y sencilla vamos a trabajar con un
objeto de cada clase y vamos a ver como se crean y de que forma ejecutan sus método.
Para ello empecemos mostrando el siguiente fragmento de código:
public class Main {
// ArrayList de objetos SeleccionFutbol. Idenpendientemente de la clase hija a la que pertenezca el objeto
public static ArrayList<SeleccionFutbol> integrantes = new ArrayList<SeleccionFutbol>();
public static void main(String[] args) {
Entrenador delBosque = new Entrenador(1, "Vicente", "Del Bosque", 60, "284EZ89");
Futbolista iniesta = new Futbolista(2, "Andres", "Iniesta", 29, 6, "Interior Derecho");
Masajista raulMartinez = new Masajista(3, "Raúl", "Martinez", 41, "Licenciado en Fisioterapia", 18);
integrantes.add(delBosque);
integrantes.add(iniesta);
integrantes.add(raulMartinez);
// CONCENTRACION
System.out.println("Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre()+" "+integrante.getApellidos()+" -> ");
integrante.Concentrarse();
}
// VIAJE
System.out.println("nTodos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre()+" "+integrante.getApellidos()+" -> ");
integrante.Viajar();
}
......
Lo primero que vemos es que nos creamos un objeto de cada clase, pasándole
los atributos al constructor como parámetro y después “sorprendentemente” los
metemos en un “ArrayList” de objetos de la clase “SeleccionFutbol” que es la
clase padre. Esto evidentemente te lo permite hacer ya que todos los objetos son hijos
de la misma clase padre. Luego como veis, recorremos el ArrayList y ejecutamos sus
métodos “comunes” como son el ‘Concentrarse’ y el ‘Viajar’. Este código da como salida
lo siguiente:
Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)
Vicente Del Bosque -> Concentrarse
Andres Iniesta -> Concentrarse
Raúl Martinez -> Concentrarse
Todos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)
Vicente Del Bosque -> Viajar
Andres Iniesta -> Viajar
Raúl Martinez -> Viajar
Como veis al ejecutar todos el mismo método de la clase padre el código puesto
funciona correctamente.
Posteriormente vamos a ejecutar código especifico de las clases hijas, de ahi
que ahora no podamos recorrer el ArrayList y ejecutar el mismo método para todos los
objetos ya que ahora esos objetos son únicos de la clases hijas. El código es el
siguiente:
// ENTRENAMIENTO
System.out.println("nEntrenamiento: Solamente el entrenador y el futbolista tiene metodos para entrenar:");
System.out.print(delBosque.getNombre()+" "+delBosque.getApellidos()+" -> ");
delBosque.dirigirEntrenamiento();
System.out.print(iniesta.getNombre()+" "+iniesta.getApellidos()+" -> ");
iniesta.entrenar();
// MASAJE
System.out.println("nMasaje: Solo el masajista tiene el método para dar un masaje:");
System.out.print(raulMartinez.getNombre()+" "+raulMartinez.getApellidos()+" -> ");
raulMartinez.darMasaje();
// PARTIDO DE FUTBOL
System.out.println("nPartido de Fútbol: Solamente el entrenador y el futbolista tiene metodos para el partido de fútbol:");
System.out.print(delBosque.getNombre()+" "+delBosque.getApellidos()+" -> ");
delBosque.dirigirPartido();
System.out.print(iniesta.getNombre()+" "+iniesta.getApellidos()+" -> ");
iniesta.jugarPartido();
Como vemos aunque el entrenador y los futbolistas asistan a un entrenamiento,
los dos hacen una función diferente en el mismo, por tanto hay que hacer métodos
diferente para cada una de las clases. Ya veremos cuando hablemos del polimorfismo
que podremos ejecutar el mismo método para clases diferentes y que esos métodos
hagan cosas distintas. Como resultado al código mostrado tenemos lo siguiente:
Entrenamiento: Solamente el entrenador y el futbolista tiene metodos para entrenar:
Vicente Del Bosque -> Dirige un entrenamiento
Andres Iniesta -> Entrena
Masaje: Solo el masajista tiene el método para dar un masaje:
Raúl Martinez -> Da un masaje
Partido de Fútbol: Solamente el entrenador y el futbolista tiene metodos para el partido de fútbol:
Vicente Del Bosque -> Dirige un partido
Andres Iniesta -> Juega un partido
CONCLUSIONES Y ACLARACIONES:
Esto ha sido todo lo que hemos contado sobre la herencia en esta entrada. El
tema de la herencia es un tema que puede ser un poco más complejo de lo que lo
hemos contado aquí, ya que solo hemos contado lo que es la herencia simple (ya que
Java por el momento es el único tipo de herencia que soporta) y no la herencia múltiple,
que es un tipo de herencia en la que una clase hija puede tener varios padres, aunque
por el momento si estáis empezando a aprender el concepto de la herencia, con la
herencia simple tenéis más que suficiente. Para los que os estéis iniciando en el mundo
de la ingeniería informática, habréis podido ver que hemos puesto unos ejemplo
mostrando unos diagramas “un poco raros”; pues bien, estos diagramas se llaman
diagramas de clases (que los hemos realizado con la herramienta web
de www.genmymodel.com) y sirven para representar de forma gráfica los atributos y
métodos de las clases y las relaciones entre ellos, utilizando el lenguaje UML del cual
intentaremos hablar más adelante en otros tutoriales. Por último decir y aclarar que en
esta entrada quizás no hemos utilizado una terminología correcta para explicar la
herencia, pero lo hemos explicadode una forma algo distinta a como esta explicada por
ahi para que los que empeceis podais entender la herencia desde otro punto de vista.
Polimorfismo en Java -Interface- (Parte II), con ejemplos
El proyecto de este post lo puedes descargar pulsando AQUI.
En esta entrada vamos a continuar hablando de polimorfismo y en concreto
vamos a explicar las “Interface” y como siempre lo vamos ha hacer con un ejemplo.
Para entender lo que vamos a contar en esta entrada es imprescindible que sepais que
es la herencia y el polimorfismo, por tanto sino teneis muy claros estos conceptos
recomendamos que mireis los siguientes tutoriales en los que hablamos sobre ello:
Herencia -> Herencia en Java, con ejemplos
Polimorfismo -> Polimorfismo en Java (Parte I), con ejemplos
El concepto de Interface lleva un paso más alla el concepto de una clase
abstracta en la que vimos queuna clase abstracta es una clase que no se puede
instanciar (crear un objeto de esa clase) pero si se pueden definir atributos e
implementar métodos en ella para que sus clases hijas los puedan utilizar. Pues
bien una Interface es una clase abstracta pura en la que todos sus métodos son
abstractos y por tanto no se pueden implementar en la clase Interface. Mucho
podreis pensar para que vale una clase abstracta pura en la que no se permiten
implementar métodos y que encima en las clases hijas de esa interface tengan que
tener “si o si” implementados estos métodos; pues bien, las Interfaces sirven para
establecer la forma que debe de tener una clase. Un ejemplo que hay en Java sobre
Interace es la Interface Map. En la entrada “Map en Java, con ejemplos“, vimos que
habia diferentes tipos de Map; en concreto los HashMap, TreeMap y LinkedHashMap,
lo que quiere decir que todas las clases Map deben de tener implementadas a su
manera los mismo métodos como el “put()”, “get()”, “remove()”, etc. y asi lo vimos en
esta entrada en la que un HashMap inserta los datos de manera diferente a un TreeMap
y al LinkedHashMap. Por tanto vemos como se ha establecido una forma común que
deben de respetar todos los Maps de Java.
Por otro lado se ha de decir que una Interface no se pueden definir atributos
salvo que estos sean estaticos o constantes; es decir, “static” o “final”.
Siguiendo con los ejemplos que hemos estado poniendo en las entradas de
la Herencia y elPolimorfismo, vamos a ver un ejemplo en el que simularemos el
comportamiento que tendrían los diferentes integrantes de la selección española de
fútbol; tanto los Futbolistas como el cuerpo técnico (Entrenadores, Masajistas, etc…).
Para este ejemplo nos vamos a basar en el siguiente diagrama de clases:
Lo primero que vemos es la clase “IntegranteSeleccionFutbol” que es una
Interface en la que tiene definido cuatro métodos (es decir una clase abstracta pura).
Como se ha dicho en esta clase solo se definen los métodos pero no se implementan;
así que a nivel de código la clase quedaria de la siguiente manera:
public interface IntegranteSeleccionFutbol {
void concentrarse();
void viajar();
void entrenar();
void jugarPartido();
}
Lo siguiente que vemos en el diagrama de clases es la clase “SeleccionFutbol”
que es una clase abstracta que utiliza (implements) la Interface
“IntegranteSeleccionFutbol”. Al ser esta una clase abstracta no se puede instanciar
y por tanto en ella no es necesario implementar los métodos de la “Interfece”; pero si
será obligatorio implementarlo en sus clases hijas (Futbolista, Entrenador y Masajista).
Para que veamos bien el uso de las interfaces, vamos ha hacer primero una “chapuzilla”
para entender su utilización. Lo que vamos ha hacer es “no implementar los métodos
de la interface en la clase abstracta SeleccionFutbol” con el fin de que veamos que en
las clases hijas nos van a exigir que implementemos los métodos de la interface. Para
ello la clase abstracta quedaría de la siguiente forma (sin ningún método
implementado):
public abstract class SeleccionFutbol implements IntegranteSeleccionFutbol {
protected int id;
protected String nombre;
protected String apellidos;
protected int edad;
public SeleccionFutbol() {
}
public SeleccionFutbol(int id, String nombre, String apellidos, int edad) {
this.id = id;
this.nombre = nombre;
this.apellidos = apellidos;
this.edad = edad;
}
// getter y setter
}
En este punto vemos una nueva palabra reservada como es la palabra
“implements” que quiere decir que la clase “SeleccionFutbol” debe ser una clase que
adopte la forma que tiene la Interface “IntegranteSeleccionFutbol”; es decir, que debe
tener implementados los métodos de la Interface. En este caso al ser una clase
abstracta las podemos implementar ahi (si queremos) o en las clases hijas
(obligatoriamente sino las implementamos en la clase padre), por eso el ejemplo que
ponemos es para ver como el compilador nos exigirá que esos métodos estén
implementados en las clases hijas.
Por tanto sino implementamos los métodos en la clase padre
(“SeleccionFutbol”) y nos vamos a cualquiera de las clases hijas (por ejemplo la de
futbolista) vemos como nos exigen que implementemos esos métodos:
Vemos en la clase futbolista (en el IDE de Eclipse) que nos da un error y como
solución nos dice que tenemos que implementar los métodos “concentrarse()”, “viajar()”,
etc. Si en Eclipse seleccionamos una de las opciones para solucionar el error, vemos
que nos da la opción de “Añadir los métodos no implementados” (add unimplemented
methods):
Si seleccionamos esa opción vemos como en Eclipse nos implementa los
métodos de la Interface de forma automática:
Como ya hemos visto que nos exigen implementar los métodos en la Interface
en las clases hijas, ahora vamos ha hacer las cosas bien y vamos a implementar los
métodos de la Interface en la clase padre (“SeleccionFutbol”) para que solamente haya
que implementar los métodos que queramos especializarlos en las clases hijas. Por
tanto la clase “SeleccionFutbol” bien implementada quedaria de la siguiente forma:
public abstract class SeleccionFutbol implements IntegranteSeleccionFutbol {
protected int id;
protected String nombre;
protected String apellidos;
protected int edad;
// Constructor, getter y setter
public void concentrarse() {
System.out.println("Concentrarse (Clase Padre)");
}
public void viajar() {
System.out.println("Viajar (Clase Padre)");
}
public void entrenar() {
System.out.println("Entrenar (Clase Padre)");
}
public void jugarPartido() {
System.out.println("Asiste al Partido de Fútbol (Clase Padre)");
}
}
Y en las clases hijas solo implementaremos los métodos de la clase padre que
queramos redefinir, quedando las clases hijas de la siguiente forma (recordar que la
etiqueta “@Override” significa que ese método esta siendo redefinido)::
public class Futbolista extends SeleccionFutbol {
private int dorsal;
private String demarcacion;
// Constructor, getter y setter
@Override
public void entrenar() {
System.out.println("Realiza un entrenamiento (Clase Futbolista)");
}
@Override
public void jugarPartido() {
System.out.println("Juega un Partido (Clase Futbolista)");
}
public void entrevista() {
System.out.println("Da una Entrevista");
}
}
public class Entrenador extends SeleccionFutbol {
private int idFederacion;
// Constructor, getter y setter
@Override
public void entrenar() {
System.out.println("Dirige un entrenamiento (Clase Entrenador)");
}
@Override
public void jugarPartido() {
System.out.println("Dirige un Partido (Clase Entrenador)");
}
public void planificarEntrenamiento() {
System.out.println("Planificar un Entrenamiento");
}
}
public class Masajista extends SeleccionFutbol {
private String titulacion;
private int aniosExperiencia;
// Constructor, getter y setter
@Override
public void entrenar() {
System.out.println("Da asistencia en el entrenamiento (Clase Masajista)");
}
public void darMasaje() {
System.out.println("Da un Masaje");
}
}
Llegados a este punto ya tenemos implementada las misma funcionalidad que
hicimos en la entrada de Polimorfismo en Java (Parte I), con ejemplos pero diseñada
e implementada de otra forma, así que vamos a poner el código que escribimos para
esa entrada y el resultado de la ejecución del mismo para que podais ver el resultado:
public class Main {
// ArrayList de objetos SeleccionFutbol. Idenpendientemente de la clase hija a la que pertenezca el objeto
public static ArrayList integrantes = new ArrayList();
public static void main(String[] args) {
SeleccionFutbol delBosque = new Entrenador(1, "Vicente", "Del Bosque", 60, 28489);
SeleccionFutbol iniesta = new Futbolista(2, "Andres", "Iniesta", 29, 6, "Interior Derecho");
SeleccionFutbol raulMartinez = new Masajista(3, "Raúl", "Martinez", 41, "Licenciado en Fisioterapia", 18);
integrantes.add(delBosque);
integrantes.add(iniesta);
integrantes.add(raulMartinez);
// CONCENTRACION
System.out.println("Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.concentrarse();
}
// VIAJE
System.out.println("nTodos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.viajar();
}
// ENTRENAMIENTO
System.out.println("nEntrenamiento: Todos los integrantes tienen su función en un entrenamiento (Especialización)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.entrenar();
}
// PARTIDO DE FUTBOL
System.out.println("nPartido de Fútbol: Todos los integrantes tienen su función en un partido (Especialización)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.jugarPartido();
}
// PLANIFICAR ENTRENAMIENTO
System.out.println("nPlanificar Entrenamiento: Solo el entrenador tiene el método para planificar un entrenamiento:");
System.out.print(delBosque.getNombre() + " " + delBosque.getApellidos() + " -> ");
((Entrenador) delBosque).planificarEntrenamiento();
// ENTREVISTA
System.out.println("nEntrevista: Solo el futbolista tiene el método para dar una entrevista:");
System.out.print(iniesta.getNombre() + " " + iniesta.getApellidos() + " -> ");
((Futbolista) iniesta).entrevista();
// MASAJE
System.out.println("nMasaje: Solo el masajista tiene el método para dar un masaje:");
System.out.print(raulMartinez.getNombre() + " " + raulMartinez.getApellidos() + " -> ");
((Masajista) raulMartinez).darMasaje();
}
}
Como resultado a la ejecución de este programa tenemos lo siguiente:
Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)
Vicente Del Bosque -> Concentrarse (Clase Padre)
Andres Iniesta -> Concentrarse (Clase Padre)
Raúl Martinez -> Concentrarse (Clase Padre)
Todos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)
Vicente Del Bosque -> Viajar (Clase Padre)
Andres Iniesta -> Viajar (Clase Padre)
Raúl Martinez -> Viajar (Clase Padre)
Entrenamiento: Todos los integrantes tienen su función en un entrenamiento (Especialización)
Vicente Del Bosque -> Dirige un entrenamiento (Clase Entrenador)
Andres Iniesta -> Realiza un entrenamiento (Clase Futbolista)
Raúl Martinez -> Da asistencia en el entrenamiento (Clase Masajista)
Partido de Fútbol: Todos los integrantes tienen su función en un partido (Especialización)
Vicente Del Bosque -> Dirige un Partido (Clase Entrenador)
Andres Iniesta -> Juega un Partido (Clase Futbolista)
Raúl Martinez -> Asiste al Partido de Fútbol (Clase Padre)
Planificar Entrenamiento: Solo el entrenador tiene el método para planificar un entrenamiento:
Vicente Del Bosque -> Planificar un Entrenamiento
Entrevista: Solo el futbolista tiene el método para dar una entrevista:
Andres Iniesta -> Da una Entrevista
Masaje: Solo el masajista tiene el método para dar un masaje:
Raúl Martinez -> Da un Masaje
CONCLUSIONES Y ACLARACIONES:
Como hemos visto el concepto de la Interface va un paso más alla en lo referente
al concepto de la clase abstracta. Es una muy buena práctica de diseño la utilización
de clases Interface para definir clases que tengan una misma forma, aunque en ellas
realicen comportamientos distintos.
Al igual que comentamos en las conclusiones de la entrada del polimorfismo, es
muy probable que los que empeceis con la POO no le veais mucho sentido al tema de
las clases Interface, así que no os preocupeis si eso es asi porque estos conceptos se
consolidan a base de experiencia y de ir adquiriendo más conocimientos de arquitectura
y diseño del software.
Polimorfismo en Java (Parte I), con ejemplos
El proyecto de este post lo puedes descargar pulsando AQUI.
El Polimorfismo es uno de los 4 pilares de la programación orientada a objetos
(POO) junto con la Abstracción, Encapsulación y Herencia. Para entender que es el
polimorfismo es muy importante que tengáis bastante claro el concepto de la Herencia,
por tanto recomendamos que veáis la entrada en la que hablamos de la
Herencia: Herencia en Java, con ejemplos.
Para empezar con esta entrada, se ha de decir que el
término “Polimorfismo” es una palabra de origen griego que
significa “muchas formas”. Este termino se utiliza en la POO para “referirse a la
propiedad por la que es posible enviar mensajes sintácticamente iguales
a objetos de tipos distintos“. Como esta definición quizás sea algo difícil de entender,
vamos a explicarla con el ejemplo que pusimos en la entrada de la herenciaen la que
queríamos simular el comportamiento que tendrían los diferentes integrantes de la
selección española de fútbol; tanto los Futbolistas como el cuerpo técnico
(Entrenadores, Masajistas, etc…). Para este ejemplo nos vamos a basar en el siguiente
diagrama de clases:
NOTA: en este diagrama y en adelante no vamos a poner los constructores y métodos getter y setter con el fin de que el diagrama nos quede grande e “intendible” aunque en un buen diagrama de clases deberían aparecer para respetar el principio de encapsulación de la POO
En este ejemplo vamos a tener una clase padre (SelecciónFutbol) en la que
tendremos los atributos y métodos comunes a todos los integrantes que forman
la selección española de fútbol (Futbolistas, Entrenadores, Masajistas, etc.) y en
ella se van a implementar los métodos del comportamiento “genérico” que deben
de tener todos los integrantes de la selección. Como ya dijimos en la entrada de la
herencia, la herencia no es más que sacar “factor común” del código que escribimos,
así que los atributos y métodos de la clase SeleccionFutbol los tendrán también los
objetos de las clases Futbolista, Entrenador y Masajista. Antes de seguir vamos a
mostrar el código de la clase “SeleccionFutbol” para ver algunas peculiaridades:
public abstract class SeleccionFutbol {
protected int id;
protected String nombre;
protected String apellidos;
protected int edad;
// constructores, getter y setter
public void viajar() {
System.out.println("Viajar (Clase Padre)");
}
public void concentrarse() {
System.out.println("Concentrarse (Clase Padre)");
}
// IMPORTANTE -> METODO ABSTRACTO => no se implementa en la clase abstracta pero si en la clases hijas
public abstract void entrenamiento();
public void partidoFutbol() {
System.out.println("Asiste al Partido de Fútbol (Clase Padre)");
}
}
Lo primero que nos debe de llamar la atención al ver este código es que
utilizamos dos veces la palabra reservada “abstract“. Esta palabra nos indica que la
clase “SeleccionFutbol” es una clase abstracta y las clases abstractas no se
pueden instanciar, por tanto nunca podremos hacer un “new SeleccionFutbol()”. Otra
cosa que vemos es que también utilizamos la palabra reservada abstract en un
método (en el método entrenamiento). Esto quiere decir que todas las clases hijas
de la clase “SeleccionFubol” tienen que tener implementado ese método
obligatoriamente. Por tanto con esto que se acaba de contar y diciendo que la palabra
“Polimorfismo” significa “muchas formas”, podéis deducir que la clase
“SeleccionFutbol” es una clase que puede adoptar diferentes formas y en este
ejemplo puede adoptar las formas de “Futbolista”, “Entrenador” y “Masajista”.
Como vemos un “Entrenador”, un “Futbolista” y un “Masajista” pertenecen a la
misma clase padre y por eso se instancian diciendo que es una SeleccionFutbol y son
nuevos objetos de las clases hijas. Por otro lado vemos que no se pueden crear
objetos de una clase abstracta, por tanto el crearnos el objeto “casillas” nos da un
error.
Y ahora si hemos dicho que hemos definido en la clase padre un método
abstracto que es obligatorio implementar en las clases hijas ¿Como lo hacemos?.
Bueno vamos por partes. Una cosa muy buena que tiene la herencia y el polimorfismo,
es que las clases hijas no solo heredan los métodos (o la implementación de los
métodos) de las clases padre, sino que las clases hijas se pueden especializar. Esto
significa que una clase hija puede “redefinir” los métodos de su clase padre; es decir,
que se puede volver a escribir ese método y de ahi la especialización. Para ello vamos
a ver la implementación de las clases hijas:
public class Futbolista extends SeleccionFutbol {
private int dorsal;
private String demarcacion;
// constructor, getter y setter
@Override
public void entrenamiento() {
System.out.println("Realiza un entrenamiento (Clase Futbolista)");
}
@Override
public void partidoFutbol() {
System.out.println("Juega un Partido (Clase Futbolista)");
}
public void entrevista() {
System.out.println("Da una Entrevista");
}
}
public class Entrenador extends SeleccionFutbol {
private int idFederacion;
// constructor, getter y setter
@Override
public void entrenamiento() {
System.out.println("Dirige un entrenamiento (Clase Entrenador)");
}
@Override
public void partidoFutbol() {
System.out.println("Dirige un Partido (Clase Entrenador)");
}
public void planificarEntrenamiento() {
System.out.println("Planificar un Entrenamiento");
}
}
public class Masajista extends SeleccionFutbol {
private String titulacion;
private int aniosExperiencia;
// constructor, getter y setter
@Override
public void entrenamiento() {
System.out.println("Da asistencia en el entrenamiento (Clase Masajista)");
}
public void darMasaje() {
System.out.println("Da un Masaje");
}
}
Como vemos en el código todas las clases hijas tienen implementada el método
“entrenamiento()” ya que como dijimos al tenerlo en la clase padre como método
abstracto, es obligatorio que todas las clases hijas tengan ese método. Por otro lado
observamos en el código que encima del método “entrenamiento()” y otros métodos,
tenemos la etiqueta “@Override“. Esta etiqueta sirve para indicar en el código que
estamos “re-escribiendo o especializando” un método que se encuentra en la
clase padre y que queremos redefinir en la clase hija. Si os fijáis esta etiqueta solo
y exclusivamente esta en los métodos de las clases hijas que tenemos definida en la
clase padre, por tanto cuando se llame a esos métodos, las clases hijas ejecutaran el
método redefinido en la clase hija y las que no lo hayan redefinido se ejecutará es
método de la clase padre. En la siguiente imagen vemos como hacemos estas
especializaciones:
Con todo esto ya podemos empezar a ejecutar el programa que simulará el
comportamiento de los integrantes de la selección española y ver las diferentes formas
que adoptan cada uno de los integrantes de la selección. Para ello empecemos
mostrando el siguiente fragmento de código:
public class Main {
// ArrayList de objetos SeleccionFutbol. Idenpendientemente de la clase hija a la que pertenezca el objeto
public static ArrayList<SeleccionFutbol> integrantes = new ArrayList<SeleccionFutbol>();
public static void main(String[] args) {
SeleccionFutbol delBosque = new Entrenador(1, "Vicente", "Del Bosque", 60, 28489);
SeleccionFutbol iniesta = new Futbolista(2, "Andres", "Iniesta", 29, 6, "Interior Derecho");
SeleccionFutbol raulMartinez = new Masajista(3, "Raúl", "Martinez", 41, "Licenciado en Fisioterapia", 18);
integrantes.add(delBosque);
integrantes.add(iniesta);
integrantes.add(raulMartinez);
// CONCENTRACION
System.out.println("Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.concentrarse();
}
// VIAJE
System.out.println("nTodos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.viajar();
}
.........
}
Como vemos nos hemos creado tres objetos de la clase SeleccionFutbol que
adoptan una de las tres formas que pueden adaptar (Entrenador, Futbolista y
Masajista) y los metemos en un “ArrayList” de objetos de la clase “SeleccionFutbol”.
Ahora al ejecutar este fragmento de código vamos a ver que todos tienen el mismo
comportamiento a la hora de “concentrarse()” y “viajar()”, por tanto ejecutarán el método
de la clase padre:
Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)
Vicente Del Bosque -> Concentrarse (Clase Padre)
Andres Iniesta -> Concentrarse (Clase Padre)
Raúl Martinez -> Concentrarse (Clase Padre)
Todos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)
Vicente Del Bosque -> Viajar (Clase Padre)
Andres Iniesta -> Viajar (Clase Padre)
Raúl Martinez -> Viajar (Clase Padre)
Hasta el momento nada nuevo y sorprendente, pero ahora vamos a ver como
cada uno de los integrante al lanzarse los mismos métodos (“entrenamiento()” y
“partidoFutbol()”) tienen un comportamiento diferente:
........
// ENTRENAMIENTO
System.out.println("nEntrenamiento: Todos los integrantes tienen su función en un entrenamiento (Especialización)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.entrenamiento();
}
// PARTIDO DE FUTBOL
System.out.println("nPartido de Fútbol: Todos los integrantes tienen su función en un partido (Especialización)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.partidoFutbol();
}
........
Vemos el resultado al ejecutar este fragmento de código:
Entrenamiento: Todos los integrantes tienen su función en un entrenamiento (Especialización)
Vicente Del Bosque -> Dirige un entrenamiento (Clase Entrenador)
Andres Iniesta -> Realiza un entrenamiento (Clase Futbolista)
Raúl Martinez -> Da asistencia en el entrenamiento (Clase Masajista)
Partido de Fútbol: Todos los integrantes tienen su función en un partido (Especialización)
Vicente Del Bosque -> Dirige un Partido (Clase Entrenador)
Andres Iniesta -> Juega un Partido (Clase Futbolista)
Raúl Martinez -> Asiste al Partido de Fútbol (Clase Padre)
En este caso vemos que todos los integrantes ejecutan el método
“entrenamiento()” de forma diferente ya que al ser este método abstracto en la clase
padre, les forzamos a las clases hijas a que implementen ese método. Por el contrario
al ejecutar el método “partidoFutbol()” vemos que el objeto de la clase Masajista utiliza
el método implementado en la clase padre y en cambio los objetos de la clase Futbolista
y Entrenador ejecutan sus método “re-implementados o especializados” que se
volvieron a escribir en sus clases.
Por último vamos a ver que cada uno de los objetos puede ejecutar métodos
propios que solamente ellos los tienen como son el caso de “planificarEntrenamiento(),
entrevista() y darMasaje()” que solo los pueden ejecutar objetos de la clase Entrenador,
Futbolista y Masajista respectivamente:
........
// PLANIFICAR ENTRENAMIENTO
System.out.println("nPlanificar Entrenamiento: Solo el entrenador tiene el método para planificar un entrenamiento:");
System.out.print(delBosque.getNombre() + " " + delBosque.getApellidos() + " -> ");
((Entrenador) delBosque).planificarEntrenamiento();
// ENTREVISTA
System.out.println("nEntrevista: Solo el futbolista tiene el método para dar una entrevista:");
System.out.print(iniesta.getNombre() + " " + iniesta.getApellidos() + " -> ");
((Futbolista) iniesta).entrevista();
// MASAJE
System.out.println("nMasaje: Solo el masajista tiene el método para dar un masaje:");
System.out.print(raulMartinez.getNombre() + " " + raulMartinez.getApellidos() + " -> ");
((Masajista) raulMartinez).darMasaje();
........
Como resultado de la ejecución de este fragmento de código tenemos lo
siguiente:
Planificar Entrenamiento: Solo el entrenador tiene el método para planificar un entrenamiento:
Vicente Del Bosque -> Planificar un Entrenamiento
Entrevista: Solo el futbolista tiene el método para dar una entrevista:
Andres Iniesta -> Da una Entrevista
Masaje: Solo el masajista tiene el método para dar un masaje:
Raúl Martinez -> Da un Masaje
CONCLUSIONES Y ACLARACIONES:
Como hemos visto el polimorfismo es un concepto un poco más avanzado que
la herencia y puede ser muy util a la hora de jerarquizar y querer dar un patrón de
comportamiento común a una serie de objetos que heredan de la misma clase. En esta
entrada no hemos visto todo lo referente al polimorfismo ya que nos quedaría ver un
concepto un poco más avanzado en Java (y en otros lenguajes también) como son las
“Interface” (clases abstractas puras) de las cuales hablaremos en otra entrada para
terminar de ver lo que es el polimorfismo.
Por último es muy probable para los que estéis empezando con la POO que no
veáis mucho sentido a esto del polimorfismo y al principio es normal. Solo os debo de
decir que a base de experiencia se le encuentra sentido al polimorfismo, por tanto si
teneis que hacer alguna práctica en la universidad o lo que sea en la que tengais que
usar el polimorfismo intentar entenderlo y hacer lo que os pidan porque entenderlo
100% es complicado y se requiere de experiencia para ello.