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

Código que elimina el polimorfismo Python: Polimorfismo

En esta lección vamos a hablar sobre algo muy importante cuando estamos escribiendo programas reutilizables y fáciles de mantener: el polimorfismo.

Vamos a ver algunos errores comunes que pueden romper o limitar el uso del polimorfismo, y explicaremos con ejemplos cómo evitarlos. No te preocupes si el término suena complicado; vamos paso a paso.


¿Qué es el polimorfismo?

Antes de meternos en los errores, recordemos de manera sencilla qué es el polimorfismo.

El polimorfismo significa que podemos usar diferentes objetos de distintas clases como si fueran del mismo tipo, siempre y cuando compartan una misma interfaz o comportamiento.

Por ejemplo, si varios objetos tienen un método llamado .saludar(), podemos llamarlo sin importar de qué clase provienen.


Casos que rompen el polimorfismo

Vamos a analizar 3 situaciones comunes en las que, sin darnos cuenta, podríamos romper el polimorfismo:

  1. Cuando creamos objetos directamente dentro de funciones
  2. Cuando hacemos comprobaciones de tipo con isinstance
  3. Cuando usamos demasiadas condiciones (if/else)

1. Crear objetos dentro de funciones

Miremos este ejemplo:

class EmailSender:
    def send(self, email, message):
        return f"Enviando '{message}' a {email}"

class User:
    def __init__(self, email):
        self.email = email

    def get_email(self):
        return self.email

def saludar_por_email(usuario):
    sender = EmailSender()  # creación del objeto dentro de la función
    return sender.send(usuario.get_email(), "¡Hola!")

A primera vista parece flexible. La función saludar_por_email recibe un usuario desde afuera, ¡lo cual es bueno! Pero hay un detalle que rompe el polimorfismo: creamos internamente un objeto EmailSender.

¿Por qué eso puede ser un problema?

Porque no podemos cambiar esa lógica sin editar la función. Si queremos usar otro tipo de “sender” (quizás un SMSSender o un NotificationSender), no podemos hacerlo sin modificar el código fuente.

¿Qué deberíamos hacer?

Pasar el objeto como parámetro desde afuera, así mantenemos abierta la posibilidad de cambiar el comportamiento sin alterar la función:

def saludar_por_email(usuario, sender):
    return sender.send(usuario.get_email(), "¡Hola!")

Así podemos usar diferentes clases que implementen el método send sin tener que modificar la función.


2. Verificar tipos con isinstance

Este también es muy común. Veamos:

class Usuario:
    def __init__(self, nombre):
        self.nombre = nombre

class Invitado:
    pass

def saludar(persona):
    if isinstance(persona, Usuario):
        return f"Hola, {persona.nombre}!"
    elif isinstance(persona, Invitado):
        return "Hola, invitado!"
    else:
        return "¿Quién eres tú?"

Aquí estamos usando isinstance para saber qué tipo de objeto nos llegó. Aunque usamos polimorfismo al pasar objetos desde fuera, el uso de isinstance va en contra de la idea principal.

¿Qué problema hay con esto?

  • La función depende de las clases específicas.
  • Si agregamos una nueva clase (por ejemplo, Admin), debemos reescribir esta función.

Hay dos formas de mejorarlo:

Opción 1: Llevar la lógica al interior de las clases

Hacemos que cada clase se encargue de saber cómo debe ser saludada.

class Usuario:
    def __init__(self, nombre):
        self.nombre = nombre

    def saludar(self):
        return f"Hola, {self.nombre}!"

class Invitado:
    def saludar(self):
        return "Hola, invitado!"

def saludar(persona):
    return persona.saludar()

Ahora la función solo depende de que el objeto tenga un método .saludar(), sin importar su clase.

Opción 2: Usar métodos que digan lo que son

Otra forma, aunque menos recomendada, es agregar métodos que identifiquen el tipo funcional del objeto:

class Usuario:
    def __init__(self, nombre):
        self.nombre = nombre

    def is_usuario(self):
        return True

    def is_invitado(self):
        return False

class Invitado:
    def is_usuario(self):
        return False

    def is_invitado(self):
        return True

def saludar(persona):
    if persona.is_usuario():
        return f"Hola, {persona.nombre}!"
    elif persona.is_invitado():
        return "Hola, invitado!"
    else:
        return "¿Quién eres tú?"

Ya no usamos isinstance ni dependemos del tipo: simplemente llamamos métodos.


3. Muchas condiciones (if/else)

Cuando tenemos muchas condiciones en una función, suele ser señal de que podríamos aplicar mejor el polimorfismo.

Por ejemplo:

def procesar_pago(pago):
    if pago.metodo == "efectivo":
        return "Procesando en efectivo..."
    elif pago.metodo == "tarjeta":
        return "Procesando con tarjeta..."
    elif pago.metodo == "crypto":
        return "Procesando con criptomoneda..."

Lo mejor sería que cada tipo de pago tuviera su propio método .procesar() y la función principal solo llamara:

def procesar_pago(pago):
    return pago.procesar()

Esto permite que el sistema crezca sin tener que modificar una gran función cada vez que tengamos un nuevo método de pago.



Resumen

  • El polimorfismo permite usar diferentes objetos de forma intercambiable si comparten ciertos comportamientos.
  • Crear objetos dentro de funciones puede romper el polimorfismo; es mejor pasarlos como argumentos.
  • Comprobar tipos con isinstance hace que tus funciones dependan de clases específicas y pierdan flexibilidad.
  • Es mejor que los objetos tomen decisiones por sí mismos, a través de métodos que definan su comportamiento.
  • Las funciones deben estar abiertas a recibir objetos que se comporten igual, no que sean del mismo tipo.
  • Muchos if/else es una señal de que podríamos usar polimorfismo para limpiar el código.

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