Facade est un patron de conception structurel qui fournit une interface simplifiée à un sous-système complexe de classes. Dans le développement mobile, Facade est le plus souvent implémenté comme un Service Layer ou un UseCase, masquant l'interaction avec le réseau, la base de données et l'analytique. Selon Martin Fowler (Patterns of Enterprise Application Architecture, 2003), Facade est l'un des patrons clés pour organiser la couche de services.
L'essentiel
Facade est un patron structurel qui fournit une interface unifiée à un groupe d'interfaces du sous-système. Il définit une interface de haut niveau qui simplifie l'utilisation du sous-système. Facade n'ajoute pas de nouvelle fonctionnalité — il orchestre les composants existants, masquant la complexité de leur interaction au client.
// Sous-système complexe
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 — une interface simple pour 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 est une Facade qui masque AuthApi, UserDao et AnalyticsTracker au ViewModel. La UI appelle loginUser(email, password) au lieu de trois requêtes séparées vers l'API, la base de données et l'analytique. Cela réduit le couplage : si demain AuthApi devient FirebaseAuth ou si UserDao migre vers Room, seule la Facade change, pas la UI.
Service Layer est une implémentation courante de Facade dans les applications mobiles. Il encapsule la logique métier et la coordination entre les couches. Dans Android, le Service Layer est souvent implémenté via UseCase (Clean Architecture), et dans iOS via les protocoles Manager ou Service.
| Composant | Rôle dans le sous-système | Ce que masque Facade |
|---|---|---|
| AuthApi | Requête réseau vers le serveur | Format de la requête, endpoint, gestion des erreurs HTTP |
| UserDao | Stockage local du jeton | Schéma de la base de données, requêtes SQL, migrations |
| AnalyticsTracker | Envoi des événements d'analytique | SDK Firebase/AppMetrica, format des événements |
| NetworkMonitor | Vérification de la disponibilité du réseau | ConnectivityManager, BroadcastReceiver |
AuthService combine les quatre composants. Le ViewModel appelle une seule méthode sans savoir que, sous le capot, se produisent une requête réseau, une écriture en base de données, un suivi et une vérification du réseau. Lors des tests, AuthService peut être remplacé par un mock, vérifiant toute la logique d'authentification sans intégration avec les composants réels.
Facade, Adapter et Mediator sont des patrons structurels, mais ils résolvent des problèmes différents. On les confond souvent, car tous les trois introduisent un objet intermédiaire. Analysons les différences à l'aide d'un exemple d'application mobile.
| Aspect | Facade | Adapter | Mediator |
|---|---|---|---|
| Objectif | Simplifier l'interface du sous-système | Transformer une interface | Réduire le couplage entre les composants |
| Direction | Une interface → sous-système | Client → Adaptee | N composants ↔ Mediator |
| Modification de l'interface | Crée une nouvelle, simplifiée | Transforme l'existante | Ne modifie pas, coordonne |
| Le sous-système connaît-il le patron ? | Non | Non | Oui, communique via le Mediator |
| Exemple dans le développement mobile | UseCase / Service Layer | RecyclerView.Adapter | Coordinator dans iOS |
Facade ne masque pas le sous-système — le client peut accéder à AuthApi directement si nécessaire. Adapter modifie obligatoirement l'interface de l'Adaptee. Mediator coordonne les interactions complexes entre de nombreux objets qui peuvent ne pas se connaître.
L'implémentation de Facade en Kotlin pour Android avec Clean Architecture utilise UseCase comme point d'entrée pour chaque scénario métier. UseCase est une Facade qui masque le repository, le mapper et les autres dépendances à la couche UI.
// Repository est aussi une Facade, mais à un niveau inférieur
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 — une Facade pour le scénario métier
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 est une Facade pour le scénario de chargement de profil. Il masque la logique de mise en cache (local → remote), le mapping DTO → Entity → Profile et le suivi d'analytique. Le ViewModel appelle invoke(userId) et reçoit un UserProfile prêt ou une erreur. UseCase peut être testé de manière isolée en remplaçant le repository par un objet mock.
Facade dans iOS est souvent implémenté comme un Manager ou un Service. Contrairement à Android, iOS utilise des protocoles pour définir l'interface de Facade, ce qui permet de remplacer facilement les implémentations dans les tests. Examinons une Facade pour travailler avec les médias — chargement, mise en cache et affichage.
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. Vérifier le cache
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Charger les données
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Décoder
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. Enregistrer dans le cache
cache.setImage(image, for: url)
return .success(image)
}
}MediaService encapsule un processus en trois étapes : cache → chargement → décodage. La UI appelle une seule méthode loadImage(from:) au lieu de gérer ImageCache, URLSession et ImageDecoder. Lors des tests, MediaServiceProtocol peut être remplacé par un mock renvoyant des images prédéfinies sans chargement réel.
Les erreurs dans la conception de Facade annulent ses avantages : au lieu d'une simplification, on obtient un God Object dont dépend tout le système. Analysons les trois principaux problèmes.
Lorsqu'une seule Facade contient des méthodes d'authentification, de chargement de profil, d'envoi de messages et de synchronisation — c'est un God Object. Signe : plus de 15 méthodes publiques dans une classe. Solution : le diviser en plusieurs Facades spécialisées par domaines de responsabilité — AuthService, ProfileService, MessagingService.
Si la Facade renvoie des types spécifiques au sous-système (par exemple, FirebaseUser ou RealmObject), le client reste lié à une implémentation concrète. Solution : la Facade doit renvoyer uniquement ses propres types (data class / struct), en abstrayant complètement le client des détails du sous-système.
Lorsque la Facade interdit l'accès direct au sous-système, elle devient un goulot d'étranglement. Parfois, le client a besoin d'une méthode spécifique du sous-système, et le forcer à passer par la Facade est redondant. Facade ne doit pas être un gatekeeper strict : elle fournit une interface pratique, mais ne bloque pas l'accès direct aux composants.
Questions fréquentes
Facade fournit une interface simplifiée au sous-système, créant souvent un nouvel ensemble de méthodes. Proxy conserve la même interface que l'objet original, mais ajoute un contrôle d'accès ou un chargement paresseux. Facade sert à simplifier, Proxy à contrôler.
Service Layer est une implémentation du patron Facade au niveau de l'architecture de l'application. Il définit la frontière entre la UI et la logique métier, masquant les détails d'implémentation des services. Dans Android, le Service Layer est souvent implémenté via UseCase ; dans iOS, via les protocoles Manager ou Service.
God Facade apparaît lorsqu'une classe prend la responsabilité de plusieurs sous-systèmes sans lien. Signes : plus de 15 méthodes publiques, des méthodes de domaines différents (authentification + paiements + notifications), une classe difficile à tester (plus de 10 dépendances). Solution : diviser en Facades de domaine.
Dans une application avec 1-2 écrans, Facade est redondant — appeler l'API et la base de données directement depuis la UI est plus simple et plus clair. Facade est rentable avec 5+ écrans et 3+ sous-systèmes. Dans les projets petits et moyens, un Repository comme unique couche Facade suffit, sans wrapper UseCase supplémentaire.
Facade simplifie les tests, car il remplace tout un sous-système par un seul objet mock. Au lieu de mocker trois composants (réseau + base de données + analytique), il suffit de mocker une Facade. En Swift, on utilise des protocoles pour cela ; en Kotlin, des interfaces. Facade est également pratique pour les tests d'intégration qui vérifient l'orchestration des composants.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi