06 febrero 2016

Polimorfismo - Java

La siguiente entrada la expongo a medida que es algo que en teoría lo deberías tener en mente, ya que es una de las bases a tener en la práctica al desarrollar. Sin embargo, esto en muchas veces no se implementa de la mejor forma o en el peor de los casos ni si quiera se implementa y te pierdes una de las grandes ventajas de la programación orientada a objetos, sin contar de las consecuencias al no hacerlo.

La palabra polimorfismo significa en griego "muchas formas", y eso es lo que buscamos en nuestro código; que el código que desarrollemos sea flexible en el sentido que los objetos instanciados se comporte de diferentes formas; tomando el lugar, forma o comportamiento de otro objetos de su mismo tipo.

Te preguntarás, ¿qué beneficios trae esto?. Como lo he venido comentando, uno de los principios al tener en mente al desarrollar, es siempre hacerlo de una forma abstracta y dejando las implementaciones a clases concretas. Esto nos permite al final el poder realizar ajustes al código de una forma fácil, ya que un cambio solicitado en el código se realiza en las sub-clases las cuales deberán de ser siempre sustituibles por sus clases padres dentro del código.

A continuación expongo un ejemplo, en donde tenemos un servicio Pintador, el cual tiene como objetivo el exponer un servicio el cual detalla como pintar algo. Posteriormente tenemos un par de clases concretas que extienden de esta interfaz. Para que finalmente dejemos un contexto que permita realizar o invocar los servicios de pintar a partir de las clases concretas.

Como nota: considere la clase ContextPintador en su método setPintador(Pintador pintador). Ya que es aquí en donde entra el polimorfismo (toma el lugar de una sub-clase de tipo Pintador) y el casting a su vez (el cual explicare en un post siguiente).

//Esta interfaz define el servicio de pintar; ¿qué?... no lo sabemos.
public interface Pintador {
   //regresa la instrucción de como pintar algo.
   public String doPinta();

}

//Esta clase sabe como pintar cuadros.
public class PintadorCuadros implements Pintador {
   @Override
   public String doPinta() {
      return "Regresando la instrucción de como pintar un Cuadro... :)";
   }

}

//Esta Clase sabe pintar casas.
public class PintadorCasas implements Pintador {
   @Override
   public String doPinta() {
      return "Regresando la instrucción de como pintar una Casa... :)";
   }

}

//Contexto que ejecuta la instrucción de como se debe de pintar
public class ContextPintador {

   private Pintador pintador;
   public ContextPintador() {
      super();
   }
   //Pasamos la referencia de una clase concreta, en este caso toma su comportamiento.
   public void setPintador(Pintador pintador) {
      this.pintador = pintador;
   }
  //Este método no sabe como se pinta algo, en realidad la clase
no sabe nada de como pintar algo, sin embargo le delega la
responsabilidad a la interfaz Pintador la cual opera como un sustituto y una referencia de una clase concreta;
en este caso toma el comportamiento de la operación doPinta().
   public void comoPintar() {
      if (pintador != null) {
System.out.println(pintador.doPinta());
      }
   }

   public static void main(String... args) {
      ContextPintador context = new ContextPintador();
//pasamos al contexto un pintador de casas
context.setPintador(new PintadorCasas());
context.comoPintar();
//pasamos al contexto un pintador de cuadros
context.setPintador(new PintadorCuadros());
context.comoPintar();
      }

}

Al ejecutar el código tenemos como respuesta el siguiente resultado:

Regresando la instrucción de como pintar una Casa... :)
Regresando la instrucción de como pintar un Cuadro... :)

Si después por necesidad tenemos que saber la dirección de la casa que se va a pintar, solo tenemos que modificar la sub-clase PintadorCasas y todo continua sin cambios significativos.

public class PintadorCasas implements Pintador {
private String direccion;
public PintadorCasas(String direccion) {
this.direccion = direccion;
}
@Override
public String doPinta() {
return "Regresando la instrucción de como pintar una Casa... en direccion: " + direccion;
}

} 

//modificamos la clase ContextPintador 
context.setPintador(new PintadorCasas("Calle 13 num 24"));

Y ejecutando de nuevo tenemos lo siguiente:

Regresando la instrucción de como pintar una Casa... en direccion: Calle 13 num 24
Regresando la instrucción de como pintar un Cuadro... :)

Como ven la clase ContextPintador no necesito cambios, a excepción del método main, y esto es lo importante, ya que ContextPintador trabaja con una clase abstracta, en este caso la interfaz Pintador, dejando que las clases concretas se especialicen en sus necesidades u objetivos.

NOTA: el polimorfismo se logra implementar por medio de la herencia, esta se puede realizar por medio de clases o de interfaces. En lo particular yo prefiero realizar la herencia, en la mayoría de los casos, de una interfaz que de una clase concreta, ya que la primera en general no cambia muy seguido sus operaciones.

Otra cosa, notar que la clase ContextPintador opera como un patrón de diseño... lo identificas?