Facade es un patrón de diseño estructural que proporciona una interfaz simplificada a un subsistema complejo de clases. En el desarrollo móvil, Facade se implementa con mayor frecuencia como Service Layer o UseCase, ocultando la interacción con la red, la base de datos y la analítica. Según Martin Fowler (Patterns of Enterprise Application Architecture, 2003), Facade es uno de los patrones clave para organizar la capa de servicios.
Lo esencial
Facade es un patrón estructural que proporciona una interfaz unificada a un grupo de interfaces del subsistema. Define una interfaz de alto nivel que simplifica el uso del subsistema. Facade no agrega nueva funcionalidad — orquesta los componentes existentes, ocultando la complejidad de su interacción al cliente.
// Subsistema complejo
class AuthApi {
suspend fun login(email: String, pass: String): TokenResponse
}
class UserDao {
suspend fun saveUser(user: UserEntity)
suspend fun getUser(id: Long): UserEntity?
}
class AnalyticsTracker {
fun track(event: String, params: Map )
}
// Facade — una interfaz simple para la UI
class AuthService(
private val api: AuthApi,
private val dao: UserDao,
private val analytics: AnalyticsTracker
) {
suspend fun loginUser(email: String, password: String): Result {
return runCatching {
val token = api.login(email, password)
val user = User(token.userId, email, token.accessToken)
dao.saveUser(user.toEntity())
analytics.track("login_success", mapOf("method" to "email"))
user
}
}
}AuthService es un Facade que oculta AuthApi, UserDao y AnalyticsTracker del ViewModel. La UI llama a loginUser(email, password) en lugar de tres solicitudes separadas a la API, la base de datos y la analítica. Esto reduce el acoplamiento: si mañana AuthApi se convierte en FirebaseAuth o UserDao migra a Room, solo cambia el Facade, no la UI.
Service Layer es una implementación común de Facade en aplicaciones móviles. Encapsula la lógica de negocio y la coordinación entre capas. En Android, el Service Layer a menudo se implementa mediante UseCase (Clean Architecture), y en iOS mediante protocolos Manager o Service.
| Componente | Rol en el subsistema | Qué oculta Facade |
|---|---|---|
| AuthApi | Solicitud de red al servidor | Formato de la solicitud, endpoint, manejo de errores HTTP |
| UserDao | Almacenamiento local del token | Esquema de la base de datos, consultas SQL, migraciones |
| AnalyticsTracker | Envío de eventos de analítica | SDK de Firebase/AppMetrica, formato de eventos |
| NetworkMonitor | Comprobación de disponibilidad de red | ConnectivityManager, BroadcastReceiver |
AuthService combina los cuatro componentes. El ViewModel llama a un único método sin saber que debajo hay una solicitud de red, una escritura en la base de datos, un seguimiento y una comprobación de red. Al probar, AuthService puede reemplazarse por un mock, verificando toda la lógica de autenticación sin integración con componentes reales.
Facade, Adapter y Mediator son patrones estructurales, pero resuelven problemas distintos. A menudo se confunden, ya que los tres introducen un objeto intermediario. Analicemos las diferencias con el ejemplo de una aplicación móvil.
| Aspecto | Facade | Adapter | Mediator |
|---|---|---|---|
| Objetivo | Simplificar la interfaz del subsistema | Transformar una interfaz | Reducir el acoplamiento entre componentes |
| Dirección | Una interfaz → subsistema | Cliente → Adaptee | N componentes ↔ Mediator |
| Cambio de interfaz | Crea una nueva, simplificada | Transforma la existente | No la cambia, coordina |
| ¿El subsistema conoce el patrón? | No | No | Sí, se comunica a través de Mediator |
| Ejemplo en desarrollo móvil | UseCase / Service Layer | RecyclerView.Adapter | Coordinator en iOS |
Facade no oculta el subsistema — el cliente puede acceder a AuthApi directamente cuando lo necesite. Adapter cambia obligatoriamente la interfaz del Adaptee. Mediator coordina interacciones complejas entre muchos objetos que pueden no conocerse entre sí.
La implementación de Facade en Kotlin para Android con Clean Architecture usa UseCase como punto de entrada para cada escenario de negocio. UseCase es un Facade que oculta el repositorio, el mapeador y otras dependencias de la capa UI.
// Repository también es un Facade, pero de nivel inferior
class UserRepositoryImpl(
private val local: UserLocalDataSource,
private val remote: UserRemoteDataSource,
private val mapper: UserMapper
) : UserRepository {
override suspend fun getUserProfile(id: String): UserProfile {
val cached = local.getUser(id)
if (cached != null && !cached.isStale) {
return mapper.toProfile(cached)
}
val dto = remote.fetchUser(id)
val entity = mapper.toEntity(dto)
local.saveUser(entity)
return mapper.toProfile(entity)
}
}
// UseCase — un Facade para el escenario de negocio
class LoadUserProfileUseCase(
private val repo: UserRepository,
private val analytics: AnalyticsTracker
) {
suspend operator fun invoke(userId: String): Result {
return runCatching {
val profile = repo.getUserProfile(userId)
analytics.track("profile_loaded", mapOf("user_id" to userId))
profile
}
}
}LoadUserProfileUseCase es un Facade para el escenario de carga de perfil. Oculta la lógica de caché (local → remoto), el mapeo DTO → Entity → Profile y el seguimiento de analítica. El ViewModel llama a invoke(userId) y recibe un UserProfile listo o un error. El UseCase puede probarse de forma aislada, reemplazando el repositorio por un mock.
Facade en iOS a menudo se implementa como Manager o Service. A diferencia de Android, iOS usa protocolos para definir la interfaz del Facade, lo que permite reemplazar fácilmente las implementaciones en las pruebas. Veamos un Facade para trabajar con medios: carga, caché y visualización.
protocol MediaServiceProtocol {
func loadImage(from url: URL) async -> Result<UIImage, Error>
}
final class MediaService: MediaServiceProtocol {
private let cache: ImageCache
private let downloader: ImageDownloader
private let decoder: ImageDecoder
func loadImage(from url: URL) async -> Result<UIImage, Error> {
// 1. Comprobar la caché
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Cargar los datos
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Decodificar
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. Guardar en la caché
cache.setImage(image, for: url)
return .success(image)
}
}MediaService encapsula un proceso de tres pasos: caché → carga → decodificación. La UI llama a un único método loadImage(from:) en lugar de gestionar ImageCache, URLSession e ImageDecoder. Al probar, se puede reemplazar MediaServiceProtocol por un mock que devuelve imágenes preestablecidas sin carga real.
Los errores al diseñar Facade anulan sus ventajas: en lugar de simplificar, se obtiene un God Object del que depende todo el sistema. Analicemos los tres problemas principales.
Cuando un único Facade contiene métodos para autorización, carga de perfil, envío de mensajes y sincronización — esto es un God Object. Señal: más de 15 métodos públicos en una clase. Solución: dividirlo en varios Facade especializados por áreas de responsabilidad — AuthService, ProfileService, MessagingService.
Si el Facade devuelve tipos específicos del subsistema (por ejemplo, FirebaseUser o RealmObject), el cliente sigue ligado a una implementación concreta. Solución: el Facade debe devolver solo sus propios tipos (data class / struct), abstrayendo por completo al cliente de los detalles del subsistema.
Cuando el Facade prohíbe el acceso directo al subsistema, se convierte en un cuello de botella. A veces el cliente necesita un método específico del subsistema, y obligarlo a pasar por el Facade es redundante. Facade no debe ser un gatekeeper estricto: proporciona una interfaz cómoda, pero no bloquea el acceso directo a los componentes.
Preguntas frecuentes
Facade proporciona una interfaz simplificada al subsistema, a menudo creando un nuevo conjunto de métodos. Proxy conserva la misma interfaz que el objeto original, pero agrega control de acceso o carga diferida. Facade es para simplificar, Proxy para controlar.
Service Layer es una implementación del patrón Facade a nivel de arquitectura de la aplicación. Define el límite entre la UI y la lógica de negocio, ocultando los detalles de implementación de los servicios. En Android, el Service Layer a menudo se implementa mediante UseCase; en iOS, mediante protocolos Manager o Service.
God Facade surge cuando una clase asume la responsabilidad de varios subsistemas no relacionados. Señales: más de 15 métodos públicos, métodos de distintos dominios (autorización + pagos + notificaciones), una clase difícil de probar (más de 10 dependencias). Solución: dividir en Facade de dominio.
En una aplicación con 1-2 pantallas, Facade es redundante — llamar a la API y a la base de datos directamente desde la UI es más simple y claro. Facade se amortiza con 5+ pantallas y 3+ subsistemas. En proyectos pequeños y medianos basta con un Repository como única capa Facade, sin envoltura adicional de UseCase.
Facade simplifica las pruebas, ya que reemplaza todo un subsistema por un único mock. En lugar de simular tres componentes (red + base de datos + analítica), basta con simular un Facade. En Swift se usan protocolos para esto; en Kotlin, interfaces. Facade también es conveniente para pruebas de integración que verifican la orquestación de componentes.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también