🔌 El Patrón de Diseño Adapter: El Traductor Universal
🧩 Patrón Adapter: El Lector de Tarjetas Universal
El patrón de diseño Adapter, podría ser de los patrones más usados y quizá de los más sencillos de entender.
Yo por si no lo sabías, soy europeo. Por estandarización, en Europa, en la mayoría de países usamos un enchufe concreto para conectar aparatos electricos a la red, pero si viajo a Londres (UK) voy a comprobar que ese enchufe es totalmente distinto y cuando vaya a cargar el smartphone no voy a poder conectarlo a la red eléctrica por incompatibilidad.
El Adaptador en Acción 🚨
El problema que he comentado anteriormente es una de las primeras cosas que vas a leer en un blog de viajes o similares al viajar a UK. Para estos casos venden un adaptador que lo que hace es justamente lo que hace el patrón de diseño Adapter.
El Adaptador (Adapter) es el objeto que tiene la clavija estandarizada en UK para conectarlo a la red eléctrica de allí, y en la otra parte tiene una clavija para conectar el cargador smartphone con estandarización europea.
El Adaptador no modifica el el cargador de móvil ni el enchufe de la pared. Simplemente traduce la interfaz del cargador a la interfaz que la conexión eléctrica de la pared espera, permitiendo poder conectar tu cargador a la electricidad. Es decir, el adaptador se encarga de conectar tu cargador de dos palitos al enchufe de 3 palitos de la pared.
Definición en Programación
En programación es lo mismo al final, el Patrón Adapter es una estructura que permite que dos objetos o clases incompatibles trabajen juntos, actuando como un traductor entre sus interfaces.
Vamos, que mediante la función spam de la interfaz SpammerInterface puedas enviar mensajes con la API de Telegram y la de WhatsApp usando la misma función spam e implementando en cada caso la llamada a la respectiva función en cada SDK.
💡 Ejemplos Sencillos (Analogía y PHP)
Analogía para Dummies: El Traductor de Documentos
Imagina que tienes un sistema que solo sabe leer documentos en formato PDF. Y ahora tu jefe te pide que el programa tiene que poder leeer un documento en formato DOCX (Microsoft Word).
- Tu Sistema (El Cliente): Solo sabe llamar a
leerPDF(). - El Documento DOCX (El Incompatible): Solo tiene un método llamado
abrirDocx(). - El Adaptador (El Traductor): Creas un Adaptador que implementa la interfaz que tu sistema espera (
leerPDF()), pero internamente, cuando lo llamas, lo que hace es llamar al métodoabrirDocx()del documento DOCX.
| Rol | Analogía (Explicación Simple) | ¿QUÉ HACE EN PROGRAMACIÓN? |
|---|---|---|
| Cliente | Tu sistema (solo pide PDF). | Llama a un método que espera (procesarDatos()). |
| Interfaz Objetivo | El formato que necesita el Cliente (PDF). | Define el método que el Cliente llama. |
| Incompatible | La clase que tiene el formato extraño (DOCX). | Tiene el método real, pero con un nombre diferente (obtenerInfo()). |
| Adaptador | El Traductor. | Implementa la Interfaz Objetivo, pero llama a los métodos del Incompatible para traducirlos. |
Ejemplo en PHP (Para Entender la Traducción)
Imagina que tienes un sistema de informes que solo sabe dibujar gráficos usando una biblioteca antigua. Ahora, quieres usar una nueva librería de gráficos más moderna.
<?php
// 1. Interfaz Objetivo (Lo que tu sistema ESPERA)
interface GeneradorGraficos
{
public function dibujarReporte();
}
// 2. Clase Incompatible (Librería Externa - La que NO encaja)
class LibreriaGraficosAntigua
{
public function mostrarDiagrama()
{
return "Gráfico Antiguo dibujado con método: mostrarDiagrama()";
}
}
// 3. El Adaptador (El Traductor)
class AdaptadorGraficosAntiguos implements GeneradorGraficos
{
public function __construct(
private LibreriaGraficosAntigua $libreriaAntigua
) {}
// Aquí es donde ocurre la MAGIA:
// Implementamos el método esperado (dibujarReporte)...
public function dibujarReporte()
{
// ... y lo 'traducimos' llamando al método que realmente existe (mostrarDiagrama).
return "ADAPTADO: " . $this->libreriaAntigua->mostrarDiagrama();
}
}
// 4. El Cliente (Tu código, que no sabe nada de LibreriaGraficosAntigua)
function generarInforme(GeneradorGraficos $grafico)
{
echo "Cliente llama a: " . $grafico->dibujarReporte() . "\n";
}
// USO:
$libreriaVieja = new LibreriaGraficosAntigua();
// Creamos el adaptador para que la vieja librería parezca una nueva
$adaptador = new AdaptadorGraficosAntiguos($libreriaVieja);
// El cliente puede usarlo sin problemas, porque el adaptador "traduce"
generarInforme($adaptador);
// Salida: Cliente llama a: ADAPTADO: Gráfico Antiguo dibujado con método: mostrarDiagrama()
🏢 Ejemplo que se podría dar en un caso real
El patrón Adapter es muy común cuando se trabaja con APIs y servicios externos, por ejemplo.
Caso Común: Integración de Pagos
Imagina que tu plataforma de comercio electrónico tiene que aceptar pagos:
- Tu Interfaz Objetivo: Tienes una interfaz que defines tú:
ProcesadorPagoscon un métodoejecutarPago(monto). - Proveedores Incompatibles: Quieres usar Stripe, PayPal, y el banco Paco.
- Stripe tiene un método llamado
chargeCustomer($amount). - PayPal tiene un método llamado
processTransaction($price). - El Banco Paco tiene un método llamado
pagamePremo($jurdeles)
- Stripe tiene un método llamado
- Los Adaptadores:
- Creas un
AdaptadorStripeque implementaProcesadorPagos. Cuando llamas a su métodoejecutarPago(monto), internamente llama aStripe->chargeCustomer($monto). - Creas un
AdaptadorPayPalque implementaProcesadorPagos. Cuando llamas a su métodoejecutarPago(monto), internamente llama aPayPal->processTransaction($monto). - Creas un
AdaptadorPacoque implementaProcessadorPagos. Cuando llamas a su métodoejecutarPago(monto), internamente llama aBancoPaco->pagamePremo($monto).
- Creas un
Resultado: Tu código principal solo ve y usa ejecutarPago(monto), sin importarle si el pago se procesa con Stripe, PayPal o el banco del Paco. Si mañana integras otro banco, solo tienes que crear un nuevo Adapter, sin tocar el código central de tu tienda.
✅ Ventajas y ❌ Desventajas
| Aspecto | Ventajas (👍) | Desventajas (👎) |
|---|---|---|
| Compatibilidad | Permite que clases con interfaces diferentes trabajen juntas (el objetivo principal). | No resuelve problemas de lógica o bugs; solo de interfaz. |
| Separación | Aísla tu código del código incompatible o de terceros. Si el tercero cambia, solo modificas el Adaptador. | Añade una capa de abstracción, lo que serán más archivos y más clases al proyecto. |
| Reutilización | Puedes reutilizar clases ya existentes sin tener que modificarlas. | Puede introducir un impacto mínimo en el rendimiento debido a la llamada indirecta. |
| Limpieza | Mantiene el código tu limpio y simple, ya que solo conoce la Interfaz Objetivo. | Algunos lo ven como un “parche” si el diseño inicial no fue modular. |
⏰ Cuándo Usarlo y Cuándo Evitarlo
¿Cuándo Usarlo? (Detección y Necesidad)
- Integrar Código de Terceros: Cuando usas una librería o API externa (como un servicio de clima, pagos, o mapas) y su forma de llamar a los métodos no coincide con la forma en que tu aplicación quiere llamarlos.
- Actualizar Sistemas: Cuando quieres reemplazar una clase vieja npmpor una nueva con una mejor funcionalidad, pero no quieres cambiar un montón de líneas de código que dependen de la clase vieja. Creas un Adaptador que hace que la nueva clase parezca la vieja.
- Herencia Compleja: Cuando tienes varias clases existentes que hacen cosas similares pero no tienen una superclase común, y quieres tratarlas de manera uniforme. El Adaptador proporciona esa uniformidad.
¿Cuándo NO Usarlo? (Errores Comunes)
- Para solucionar Lógica: Si el problema es que el código no funciona o devuelve resultados incorrectos, el Adaptador no te ayuda. Solo traduce.
- Cuando la Interfaz SÍ Coincide: Si la librería de terceros ya tiene métodos con los nombres que tú necesitas, usar un Adaptador es añadir complejidad innecesaria.
- Solucionar Problemas Menores: No lo uses para cambiar un método de
obtenerDatos()agetDatos(). Un simple cambio de nombre en tu código es más fácil si solo es un lugar. Úsalo cuando el cambio debe ser global para todo tu código cliente.
Errores Comunes al Implementarlo
- Confundir con Facade: El patrón Facade (Fachada) simplifica una subdivisión compleja de la aplicación. El Adaptador traduce una interfaz incompatible a una compatible. Son diferentes, aunque ambos envuelven código.