Programación Orientada a Objetos 9 - Polimorfismo

El polimorfismo es un principio clave en la programación orientada a objetos, permite a los objetos ser tratados como instancias de su clase padre.

En este capítulo veremos cómo el polimorfismo mejora la flexibilidad y la extensibilidad, permitiendo que objetos de diferentes clases sean tratados de manera uniforme.

¿Qué es el Polimorfismo?

El término polimorfismo proviene del griego y significa “muchas formas“.

En el contexto de la programación orientada a objetos, llamamos polimorfismo a la capacidad de que un mismo mensaje pueda invocar métodos distintos (mismo nombre de método, distinta implementación, redefinida en las subclases).

El polimorfismo retrasa la decisión sobre el tipo de objeto hasta el momento en que va a usar el método (vinculación tardía o en tiempo de ejecución), ahí decide qué objeto va a responder, usando su propia implementación.

Lo importante es que los objetos de distintas clases (derivadas de un mismo ancestro) puedan ser tratadas de la misma manera y respondan al mismo mensaje.

Esencialmente, el polimorfismo permite que un mismo mensaje sea respondido por diferentes objetos, que son vistos de la misma forma.

Sin polimorfismo

Supongamos que tenemos una serie de figuras: Círculo, Rectángulo, Triángulo. Cada una se dibuja de una manera, por lo que cada una tiene su propio método de dibujo.


  //Sin polimorfismo: 
  //    deberíamos averiguar qué tipo de figura es e invocar su método de dibujo
  public void dibujarTodas() {
      for (int i = 0; i < array.length; i++) {
          if (array[i] instanceof Circulo) {
              array[i].dibujarCirculo();
          } else if (array[i] instanceof Rectangulo) {
              array[i].dibujarRectangulo();
          } else {
              array[i].dibujarTriangulo();
          }
      }
  }
                

Si tenemos un array con todas las figuras, debemos averiguar el tipo de figura que es e invocar su método dibujar.

Esto hace que el código quede acoplado, si creamos otro tipo de figura debemos agregarlo en la condición también.

Con polimorfismo

Gracias al polimorfismo podemos tener un array de figuras, y dibujarlas todas independientemente de su tipo concreto.

polimorfismo

Cada una redefine el método dibujar de la clase padre.


  //Con polimorfismo: no importa el tipo de figura
  public void dibujarTodas() {
      for (int i = 0; i < array.length; i++) {
          array[i].dibujar();
      }
  }                                    
                

No nos interesa qué tipo de figura es, simplemente invocamos al método dibujar, y por polimorfismo va a invocar al correspondiente. 

Esto hace que nuestro código sea más simple, y lo importante, que podamos agregar más tipos de figuras sin tener que modificar los puntos en donde se usa, lo que lo hace más fácil de mantener y extender, por eso es el polimorfismo tan importante.

Sobreescritura (Redefinición) de Métodos

La sobreescritura o redefinición de métodos ocurre cuando una subclase declara un método que tiene la misma firma que un método en su superclase.

Esto permite que la subclase proporcione una implementación específica de ese método, pisando la de su ancestro.

Tiempo de Ejecución

El polimorfismo funciona en tiempo de ejecución mediante la sobreescritura de métodos.

En este caso, una clase derivada tiene un método con la misma firma que un método en su clase base, pero con una implementación diferente.

En tiempo de ejecución, se usa la implementación del tipo de objeto actual.

Ejemplo Práctico: Sistema de Notificaciones

Tenemos diferentes tipos de notificaciones: correo electrónico, SMS, notificaciones push.

Esto demostrará cómo el polimorfismo permite tratar diferentes tipos de notificaciones de una manera uniforme.


  public class Notificacion {
      public void enviarMensaje(String mensaje) {
          System.out.println("Mensaje clase Base: " + mensaje);
      }
  }                                  
                

Clases concretas


  public class EmailNotificacion extends Notificacion {
      @Override
      public void enviarMensaje(String mensaje) {
          System.out.println("Enviando email: " + mensaje);
      }
  }
  
  public class SMSNotificacion extends Notificacion {
      @Override
      public void enviarMensaje(String mensaje) {
          System.out.println("Enviando SMS: " + mensaje);
      }
  }
  
  public class PushNotificacion extends Notificacion {
      @Override
      public void enviarMensaje(String mensaje) {
          System.out.println("Notificación push: " + mensaje);
      }
  }                
                 

Servicio


  public class NotificacionServicio {
      public void enviarNotification(Notificacion notificacion, String mensaje) {
          notificacion.enviarMensaje(mensaje);
      }
  }
                

Cliente o clase de prueba


  // En el método main o cualquier otro método de prueba
  public static void main(String[] args) {
      NotificacionServicio servicio = new NotificacionServicio();

      Notificacion email = new EmailNotificacion();
      Notificacion sms = new SMSNotificacion();
      Notificacion push = new PushNotificacion();

      servicio.enviarNotificacion(email, "Este es un email.");
      servicio.enviarNotificacion(sms, "Este es un SMS.");
      servicio.enviarNotificacion(push, "Esta es una notificación push.");
  }
                

En este ejemplo, NotificacionServicio puede enviar cualquier tipo de notificación sin necesidad de saber exactamente qué tipo de notificación es.

En tiempo de ejecución, Java determinará el tipo de objeto y llamará al método enviarMensaje correspondiente. Esto es polimorfismo en acción.

Conclusiones

El polimorfismo es para muchos el concepto más importante de la programación orientada a objetos, porque permite diseñar sistemas extensibles y fácilmente mantenibles, donde los componentes pueden ser intercambiados o actualizados sin alterar el núcleo del sistema.

Al usarlo junto con otros principios de la programación orientada a objetos, podemos escribir código más limpio, más comprensible y más adaptable, que estará listo para crecer y evolucionar de acuerdo a las necesidades.

En polimorfismo da lugar a clases abstractas e interfaces, temas fundamentales que veremos en los próximos capítulos.