Regístrate para acceder a más de 15 cursos gratuitos de programación con un simulador

Composición de objetos JS: Polimorfismo

En esta lección hablaremos de un sistema que ayuda a organizar correctamente el código construido en clases.

En lenguajes donde la POO se construye sin encapsulación, estos problemas se resuelven más fácilmente y ocurren con menos frecuencia. Si quieres saber cómo sucede esto, intenta escribir código en Clojure o Elixir.

Supongamos que estamos construyendo un sitio web con un mecanismo de autenticación. Después de la autenticación, se muestra un saludo al usuario, que varía según la edad del usuario. Si el usuario es menor de 18 años, se muestra un saludo, y si es mayor de 18 años, se muestra otro.

En este caso, la implementación directa con un if sería la mejor solución para el problema. Pero en esta lección estamos practicando el uso de polimorfismo en el modelo de clases, por lo que seguiremos otro enfoque. La tarea en sí se simplifica a propósito para no perder tiempo en su análisis.

El primer impulso de muchos desarrolladores es crear dos clases: Under18(MenorDe18) y Above18(MayorDe18). Luego, en cada una de las clases, se agrega un método getMessage(). Como resultado, obtenemos polimorfismo de subtipos:

// Las clases Under18 y Above18 heredan propiedades y métodos de Usuario
class Under18 extends Usuario {
  getMessage() {
    // Hola Juan 
    return `Hola ${this.nombre}`;
  }
}

class Above18 extends Usuario {
  getMessage() {
    // Hola Sr. López
    // Hola Sra. Álvarez
    return `Hola ${this.tratamiento} ${this.nombre}`;
  }
}
// En algún lugar de la plantilla
// La clase correcta para el usuario se elige al comienzo del procesamiento de la solicitud HTTP
= usuario.getMessage()

Esta solución funciona, pero no es el camino correcto. Hoy en día tenemos menores de 18 años y mayores de 18 años, pero luego habrá un comportamiento separado para aquellos mayores de 65 años. Todo se complicará aún más cuando, además de estas divisiones, haya una división adicional por género. En ese caso, obtendremos un gran número de combinaciones, para cada una de las cuales tendremos que crear una clase de usuario separada:

  • mujeres mayores de 18 años
  • mujeres menores de 18 años
  • hombres mayores de 18 años
  • hombres menores de 18 años
  • ...

En los libros de patrones, les gusta dar el ejemplo de la división de medios de transporte por tipos: flotantes, voladores y terrestres. Y luego resulta que algunos pueden flotar y conducir al mismo tiempo.

Ahora intentemos responder por qué no debemos resolver este problema con subtipos en cualquier caso. El usuario en sí mismo es una entidad tomada de nuestro dominio. El dominio y la presentación de texto en la pantalla son cosas completamente diferentes. El segundo se refiere a la lógica de la aplicación, pero no a la lógica del negocio. Si no pensamos en esto, llegará un momento en el que el usuario contendrá todo lo que sucede en el sitio web, ya que todo está de alguna manera relacionado con el usuario. Y obtendremos un Objeto todopoderoso.

La solución correcta es la composición, un enfoque basado en la interacción de objetos, no en la jerarquía de clases. Empecemos. En nuestra tarea, hay dos situaciones: usuarios menores de 18 años y usuarios mayores. Estos son dos comportamientos diferentes, descritos por dos clases: GreetingForAbove18 (SaludoParaMayoresDe18) y GreetingForUnder18 (SaludoParaMenoresDe18). Cada clase implementa el método getMessage, que devuelve el saludo adecuado para cada categoría de usuarios.

class GreetingForUnder18 {
  getMessage(usuario) {
    return `Hola ${usuario.nombre}`;
  }
}

class GreetingForAbove18 {
  getMessage(usuario) {
    return `Hola ${usuario.tratamiento} ${usuario.nombre}`;
  }
}

¿Cómo interactuará el usuario con los objetos de estas clases? Hay dos opciones: pasarlo al constructor o al método getMessage(user). ¿Cuál es la correcta? Primero, entiende si estamos tratando con una abstracción de datos. El usuario es una abstracción de datos con unicidad y tiempo de vida. En cambio, mostrar un mensaje es una operación sin estado. La clase y su objeto existen solo para lograr polimorfismo de subtipos. Por eso, en este caso, es mejor pasar al usuario a través del método:

// En algún lugar de la plantilla
= saludo.getMessage(usuario)

Queda por resolver el problema de elegir y crear el objeto correspondiente. Esto lo hace una fábrica, que se llama en algún lugar antes de la generación de la salida desde la plantilla.

const construirObjetoSaludo = (usuario) => {
  if (usuario.getEdad() < 18) {
    return new GreetingForUnder18();
  } else {
    return new GreetingForAbove18();
  }
}

Lo principal en este esquema es que el usuario sigue siendo un usuario y solo se encarga de la lógica central de la aplicación. Incluso si se agregan nuevas condiciones para mostrar mensajes y nuestros dos clases se convierten en diez, esto no afectará al usuario.

Lo más importante es que, cuando surjan nuevas tareas no relacionadas con la visualización de mensajes, el usuario no se verá afectado. Por ejemplo, si queremos enviar correos a diferentes usuarios después de registrarse. Según la cantidad de tipos de correos, se crearán tantas clases como sean necesarias. El principio seguirá siendo el mismo: una fábrica, la selección del tipo correcto al inicio del registro y el comportamiento polimórfico al enviar el correo.

Un lector atento notará que esto se parece mucho a la estrategia. Y de hecho, lo es.

Al final, el código tendrá muchas pequeñas clases, tantas como variantes de comportamiento existan. La mayoría de estos objetos no tienen estado y son necesarios para organizar un código polimórfico.


Materiales adicionales

  1. Principio de segregación de interfaces / Wikipedia

Para acceder completo a curso necesitas un plan básico

El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.

Obtener acceso
130
cursos
1000
ejercicios
2000+
horas de teoría
3200
test

Obtén acceso

Cursos de programación para principiantes y desarrolladores experimentados. Comienza tu aprendizaje de forma gratuita

  • 130 cursos, 2000+ horas de teoría
  • 1000 ejercicios prácticos en el navegador
  • 360 000 estudiantes
Al enviar el formulario, aceptas el «Política de privacidad» y los términos de la «Oferta», y también aceptas los «Términos y condiciones de uso»

Nuestros graduados trabajan en empresas como:

Bookmate
Health Samurai
Dualboot
ABBYY