Polimorfismo en Java

Buenas tardes, en esta entrada veremos el Polimorfismo, dado un escenario, aprenderemos a desarrollar el código que demuestra el uso del polimorfismo.


Recordamos, que cualquier objeto en Java que pasa mas de un test sobre la relación Es-Un (Is-A) puede ser considerado polimorfo. Recordamos tambien, que solo hay una manera de acceder a un objeto a través de la variable de referencia, y hay ciertas cosas clave que debemos recordar sobre las referencias:

  • Una variable de referencia solo puede ser de un tipo, y una vez declarado, ese tipo no puede ser cambiado.
  • Una referencia es una variable, por lo que puede ser reasignada a otros objetos (A no ser que esta referencia sea declarada como final).
  • El tipo de una variable de referencia determina los métodos que pueden ser invocados en el objeto que la variable está referenciando.
  • Una variable de referencia puede referir a cualquier objeto del mismo tipo al declarado, o Puede referir a cualquier subtipo del tipo declarado.
  • Una variable de referencia puede ser declarada como un tipo de clase o un tipo de interface. Si la variable es declarada como un tipo de interface, puede referenciar a cualquier objeto de cualquier clase que implemente la interface.

En la entrada anterior habíamos creado una clase GameShape que era extendida por otras 2 clases, PlayerPiece y TilePiece. Ahora imaginemos que queremos animar alguna de las formas en el juego de mesa, pero no todas pueden ser animables, por lo que…¿Que haremos con la herencia de clases?.

Si pudieramos, podriamos hacer que PlayerPiece, por ejemplo, heredara de la clase GameShape y Animatable, mientras que que la clase TilePiece extendería solo de GameShape. Pero no, esto NO funciona, Java permite que solo exista herencia simple. Esto significa que una clase solo puede tener una superclase. En otras palabras, si PlayerPiece es una clase, no hay manera de hacer algo como esto:

	class PlayerPiece extends GameShape, Animatable{ //NO!
	
	}
	

Una clase no puede heredar mas de una clase. Esto significa que un padre por clase. Una clase puede tener multiples ancestros, sin embargo, una clase B podría heredar de la clase A, y una clase C podría extender una clase B.

Por tanto, si esto no funciona, ¿Qué podríamos hacer? Podríamos simplemente poner el código del método animate() en GameShape, y deshabilitar el método en las clases que no pueden ser animadas. Pero esto es una elección mala a nivel de diseño por muchas razones, incluyendo que es mas prospensa a errores, y haría que la clase GameShape fuera menos cohesiva, y esto significa que la API de GameShape advierte que todas las piezas pueden ser animadas, cuando esto no es verdadero ya que solo algunas de ellas podrán ejecutar el método animate().

Con esto ya podemos conocer al respuesta, crear una interface llamada Animatable, y que solo las subclases que puedan ser animadas implementen esta interface. Aquí podemos ver la declaración de la interface:

	public interface Animatable{
		public void animate();
	}
	

Aquí la clase PlayerPiece que implementa la Interface:

	class PlayerPiece extends GameShape implements Animatable{
		public void movePiece(){
			System.out.println("Moviendo una pieza del juego");
		}
		
		public void animate(){
			System.out.println("Animando...");
		}
		//Más código
	}
	

Ahora ya tenemos a PlayerPiece en condiciones de pasar el test Es-Un (IS-A) para ambas clases, GameShape y la interface Animatable. Esto significa que PlayerPiece puede ser tratado polimorficamente como una de las 4 cosas en el momento dado, dependiendo del tipo declarado de la variable de referencia:

  • Un Object (Ya que cualquier objeto hereda de Object).
  • Un GameShape (PlayerPiece hereda de GameShape).
  • Un PlayerPiece (Es lo que es).
  • Un Animatable (Ya que PlayerPiece implementa la interface Animatable).

Lo siguiente son declaraciones legales. Miremos atentamente:

	PlayerPiece player = new PlayerPiece();
	Object o = player;
	GameShape shape = player;
	Animatable mover = player;
	

Solo hay un objeto aquí, pero hay 4 diferentes tipos de variables de referencia, todos refiriendo a un objeto.

Recordemos esto al compilador, PlayerPiece ES-UN GameShape, por lo que el compilador diría:

Veo que el tipo declarado es PlayerPiece, y como PlayerPiece hereda de GameShape, esto significa que PlayerPiece ha heredado el método displayShape(). Entonces, PlayerPiece puede ser usado para invocar el método displayShape().

La idea interesante aquí es que cualquier clase de un arbol de herencia puede tambien implementar Animatable, lo que significa que si tenemos un método con un argumento declarado del tipo Animatable, podemos pasarcualquier objeto que implemente esta interface. Podemos con esto usar el parámetros para invocar el método animate(), pero no el método displayShape(), u otra cosa que no sea conocido al compilador basado en el tipo de referencia.

Hemos dejado una de las grandes partes de esto, la cual es aún cuando el compilador sabe del tipo de referencia declarada, la JVM en tiempo de ejecución conoce que es realmente el objeto. Y esto significa que incluso si el método displayShape() del objeto PlayerPiece es llamado usando la variable de referencia de GameShape, si la PlayerPiece sustituye el método displayShape(), la JVM invocará la versión de PlayerPiece.

Polimorficamente la invocación de métodos solo se aplica a los métodos de instancia. Podemos siempre referir a un objeto con un tipo de variable de referencia mas general (una superclase o interface), pero en tiempo de ejecución, SOLO cosas que sean dinámicamenteseleccionadas basadas en el objeto actual son métodos de instancia. Ni métodos static, ni variables. Solo los métodos de instancia sustituidos son dinámicamente invocados basados en el tipo de objeto real.


Hasta aquí una explicación sobre el polimorfismo en Java, aunque a medida que trabajemos con ello lo veremos de mejor forma y lo captaremos bien.

Sin mas, cualquier aporte o corrección es bienvenido.

Saludos!!!