El polimorfismo es un pilar fundamental de la programación orientada a objetos que permite que una sola interfaz o nombre represente diferentes comportamientos subyacentes. Aunque el concepto es universal, su implementación técnica varía significativamente dependiendo de si el lenguaje utiliza un sistema de tipos estático o dinámico.
1. Polimorfismo mediante Interfaces
En lenguajes de tipado estático, el polimorfismo suele basarse en contratos explícitos. Una interfaz define qué métodos debe tener un objeto, pero no cómo debe ejecutarlos. Esto garantiza la seguridad de tipos en tiempo de compilación.
Ejemplo en Java (Basado en Interfaces)
En este caso, definimos un contrato para diferentes sistemas de almacenamiento:
interface Almacenamiento {
void guardarDato(String info);
}
class DiscoDuro implements Almacenamiento {
public void guardarDato(String info) {
System.out.println("Escribiendo en sectores físicos: " + info);
}
}
class Nube implements Almacenamiento {
public void guardarDato(String info) {
System.out.println("Subiendo a servidor remoto: " + info);
}
}
public class Main {
public static void main(String[] args) {
Almacenamiento miRecurso = new DiscoDuro();
miRecurso.guardarDato("Archivo_01.txt"); // Ejecuta lógica de DiscoDuro
miRecurso = new Nube();
miRecurso.guardarDato("Backup_01.zip"); // Ejecuta lógica de Nube
}
}
Ejemplo en Go (Interfaces Implícitas)
A diferencia de Java, Go no requiere una declaración explícita (implements). Si una estructura posee los métodos definidos, cumple con la interfaz automáticamente.
package main
import "fmt"
type Notificador interface {
Enviar(mensaje string)
}
type Email struct{}
func (e Email) Enviar(msg string) {
fmt.Println("Enviando correo: ", msg)
}
type SMS struct{}
func (s SMS) Enviar(msg string) {
fmt.Println("Enviando mensaje de texto: ", msg)
}
func procesarAlerta(n Notificador, texto string) {
n.Enviar(texto)
}
func main() {
correo := Email{}
mensajeTexto := SMS{}
procesarAlerta(correo, "Servidor caído")
procesarAlerta(mensajeTexto, "Intento de acceso")
}
2. Polimorfismo mediante Herencia
Esta variante utiliza una clase base que contiene una implementación (aunque sea mínima) que luego es sobrescrita por las clases derivadas. Es común tanto en lenguajes estáticos como dinámicos.
Ejemplo en C++ (Funciones Virtuales)
En C++, el polimorfismo en tiempo de ejecución se logra mediante punteros a la clase base y el uso de la palabra clave virtual.
#include <iostream>
class Procesador {
public:
virtual void ejecutar() {
std::cout << "Proceso genérico iniciado." << std::endl;
}
};
class ProcesadorGrafico : public Procesador {
public:
void ejecutar() override {
std::cout << "Renderizando fotogramas en GPU." << std::endl;
}
};
int main() {
Procesador* p = new ProcesadorGrafico();
p->ejecutar(); // Llama a la versión de la subclase
delete p;
return 0;
}
3. Tipado Pato (Duck Typing)
Propio de lenguajes dinámicos como Python, el polimorfismo no depende de una jerarquía de clases o interfaces formales. Si un objeto tiene el método requerido, el languaje lo ejecuta sin importar su origen.
Ejemplo en Python
Aquí observamos cómo dos clases sin relación alguna pueden usarse polimórficamente.
class LectorPDF:
def leer(self):
return "Contenido de PDF extraído"
class LectorExcel:
def leer(self):
return "Datos de celdas procesados"
def mostrar_contenido(lector):
# No importa el tipo, solo que tenga el método 'leer'
print(lector.leer())
obj_pdf = LectorPDF()
obj_xlsx = LectorExcel()
mostrar_contenido(obj_pdf)
mostrar_contenido(obj_xlsx)
Diferencias clave según el sistema de tipos
- Lenguajes Estáticos: Requieren que los objetos compartan un tipo común (interfaz o clase padre) para ser traatdos polimórficamente. El chequeo se hace antes de ejecutar el programa.
- Lenguajes Dinámicos: El polimorfismo es más flexible y se basa en la capacidad del objeto para responder a un mensaje en tiempo de ejecución. No es estrictamente necesario heredar de una base común.