¿Alguna vez te has quedado atascado con código de una API heredada que no podías eliminar, pero tampoco usar tal cual?
Es un obstáculo habitual al conectar un módulo de red antiguo con una pantalla nueva.
En resumen, la solución más limpia es envolver la API heredada en un protocolo (interface) y colocar un adaptador entre ambos.
El patrón Adapter inserta un «convertidor» entre dos interfaces incompatibles.
Si lo confundes con patrones similares que envuelven objetos, como Facade, Proxy y Decorator, Comparativa de 4 patrones de envoltura te ayuda a distinguirlos por su propósito.
Hoy explicaré cómo aplicarlo en Swift siguiendo el proceso que viví personalmente.
Tres cosas que aprenderás
Aquí tienes primero un resumen para quienes tienen prisa.
- Definir primero como protocolo la forma que necesita el código nuevo
- Crear un tipo adaptador que convierta la API heredada para ajustarla a ese protocolo
- Hacer que las pantallas y los modelos de vista dependan solo del protocolo, no de la API heredada
Con solo seguir estas tres reglas, el alcance de los cambios se reduce mucho al reemplazar la API completa más adelante.
Después de cambiar a esta estructura, escribir pruebas me resultó mucho más sencillo.
¿Por qué hace falta el patrón Adapter en Swift?
Las API heredadas normalmente no tienen la forma que necesitamos.
Pueden basarse en callbacks, tener parámetros desordenados o devolver tipos ambiguos.
Si el código nuevo se adapta a una API antigua, todo el código se tambalea el día que esa API desaparece.
Por eso colocamos un adaptador en medio.
Supongamos que el módulo antiguo tiene este aspecto.
// Código heredado difícil de tocar API (Basado en callbacks)
class LegacyUserAPI {
func fetch(id: Int,
done: @escaping (NSDictionary?) -> Void) {
// Una llamada de red antigua...
}
}
Si llevas NSDictionary directamente hasta la pantalla, cambiar esta API después obligará a desmontar también el código de la pantalla.
A simple vista, no es una forma que apetezca conectar.
Cómo envolver una API heredada en un protocolo (3 pasos)
El proceso de envoltura es más sencillo de lo que parece.
Paso 1: define la forma deseada como protocolo.
Primero escribe la forma en que el código nuevo debería poder decir: «Así quiero llamarlo».
// La interfaz limpia que quiere el código nuevo
protocol UserRepository {
func user(id: Int) async throws -> User
}
Cambié los callbacks por async/await y NSDictionary por un tipo User.
Paso 2: el adaptador ajusta la API heredada a este protocolo.
Encierra todas las conversiones engorrosas dentro del adaptador.
// Un adaptador que convierte la API heredada al protocolo nuevo
struct LegacyUserAdapter: UserRepository {
let legacy = LegacyUserAPI()
func user(id: Int) async throws -> User {
try await withCheckedThrowingContinuation { cont in
legacy.fetch(id: id) { dict in
cont.resume(returning: User(dict))
}
}
}
}
Aquí, withCheckedThrowingContinuation, que convierte callbacks en async, desempeña el papel central.
Paso 3: la pantalla depende solo del protocolo.
El modelo de vista no necesita conocer LegacyUserAPI. Solo necesita conocer UserRepository.
Así, el código heredado aparece en un único lugar: el adaptador.
¿Qué cambia al usar un adaptador? (Comparación con llamadas directas)
He comparado en una tabla las llamadas directas con el enfoque del adaptador.
| Elemento | Llamada directa a la API heredada | Envolver con un adaptador |
|---|---|---|
| Alcance de cambios al reemplazar la API | Toda la pantalla | Un solo adaptador |
| Prueba unitaria | Difícil | Fácil con un mock |
| Legibilidad del código nuevo | Baja | Alta |
| Trabajo inicial | Poco | Aumenta un poco |
Es cierto que el trabajo inicial aumenta ligeramente.
Pero para mí, este coste mereció totalmente la pena.
Al probar, basta con inyectar un objeto falso que implemente UserRepository para validar la lógica de la pantalla sin red.
Incluso pudimos empezar a desarrollar antes de que estuviera lista la API real.
Preguntas frecuentes (Q&A)
P. ¿Debo crear el adaptador como struct o como class?
Si no tiene estado, struct es suficiente.
Si necesitas conservar el objeto heredado o compartir referencias, usa class.
P. ¿En qué se diferencia el patrón Adapter del patrón Facade?
El objetivo de Adapter es «hacer compatibles las interfaces».
Facade busca «mostrar varias partes complejas como una sola cosa sencilla», así que la orientación es algo distinta.
P. ¿Cómo debo nombrar el protocolo?
No sigas el nombre heredado; asígnale el rol que necesita el código nuevo.
Por ejemplo, usa UserRepository en lugar de LegacyUserAPI.
No intentes eliminar la API heredada a la fuerza; empieza por aislarla discretamente con un protocolo y un adaptador.
Si confinas el código antiguo en un solo lugar, la siguiente refactorización será mucho más fácil. Prueba a envolver primero la API más desordenada de tu proyecto.

