- ¿Qué es el polimorfismo?
- Casos que rompen el polimorfismo
- 1. Crear objetos dentro de funciones
- 2. Verificar tipos con isinstance
- 3. Muchas condiciones (if/else)
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.
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:
- Cuando creamos objetos directamente dentro de funciones
- Cuando hacemos comprobaciones de tipo con isinstance
- 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?
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
isinstancehace 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/elsees 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.