Facade è un pattern di progettazione strutturale che fornisce un'interfaccia semplificata a un sottosistema complesso di classi. Nello sviluppo mobile, Facade è il più delle volte implementato come Service Layer o UseCase, nascondendo l'interazione con la rete, il database e l'analisi. Secondo Martin Fowler (Patterns of Enterprise Application Architecture, 2003), Facade è uno dei pattern chiave per organizzare il livello dei servizi.
L'essenziale
Facade è un pattern strutturale che fornisce un'interfaccia unificata a un gruppo di interfacce del sottosistema. Definisce un'interfaccia di alto livello che semplifica l'uso del sottosistema. Facade non aggiunge nuove funzionalità — orchestra i componenti esistenti, nascondendo la complessità della loro interazione al client.
// Sottosistema complesso
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 — un'interfaccia semplice per 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 è una Facade che nasconde AuthApi, UserDao e AnalyticsTracker al ViewModel. La UI chiama loginUser(email, password) invece di tre richieste separate all'API, al database e all'analisi. Questo riduce l'accoppiamento: se domani AuthApi diventa FirebaseAuth o UserDao migra su Room, cambia solo la Facade, non la UI.
Service Layer è un'implementazione comune di Facade nelle applicazioni mobili. Incapsula la logica di business e il coordinamento tra i livelli. In Android, il Service Layer è spesso implementato tramite UseCase (Clean Architecture), e in iOS tramite i protocolli Manager o Service.
| Componente | Ruolo nel sottosistema | Cosa nasconde Facade |
|---|---|---|
| AuthApi | Richiesta di rete al server | Formato della richiesta, endpoint, gestione degli errori HTTP |
| UserDao | Archiviazione locale del token | Schema del database, query SQL, migrazioni |
| AnalyticsTracker | Invio di eventi di analisi | SDK Firebase/AppMetrica, formato degli eventi |
| NetworkMonitor | Verifica della disponibilità della rete | ConnectivityManager, BroadcastReceiver |
AuthService combina tutti e quattro i componenti. Il ViewModel chiama un unico metodo senza sapere che, sotto il cofano, avvengono una richiesta di rete, una scrittura nel database, un tracciamento e una verifica della rete. Durante i test, AuthService può essere sostituito con un mock, verificando l'intera logica di autenticazione senza integrazione con componenti reali.
Facade, Adapter e Mediator sono pattern strutturali, ma risolvono problemi diversi. Vengono spesso confusi, poiché tutti e tre introducono un oggetto intermediario. Analizziamo le differenze usando come esempio un'applicazione mobile.
| Aspetto | Facade | Adapter | Mediator |
|---|---|---|---|
| Obiettivo | Semplificare l'interfaccia del sottosistema | Trasformare un'interfaccia | Ridurre l'accoppiamento tra i componenti |
| Direzione | Un'interfaccia → sottosistema | Client → Adaptee | N componenti ↔ Mediator |
| Modifica dell'interfaccia | Crea una nuova, semplificata | Trasforma quella esistente | Non la modifica, coordina |
| Il sottosistema conosce il pattern? | No | No | Sì, comunica tramite il Mediator |
| Esempio nello sviluppo mobile | UseCase / Service Layer | RecyclerView.Adapter | Coordinator in iOS |
Facade non nasconde il sottosistema — il client può accedere ad AuthApi direttamente quando necessario. Adapter modifica obbligatoriamente l'interfaccia dell'Adaptee. Mediator coordina interazioni complesse tra molti oggetti che potrebbero non conoscersi tra loro.
L'implementazione di Facade in Kotlin per Android con Clean Architecture usa UseCase come punto di ingresso per ogni scenario di business. UseCase è una Facade che nasconde repository, mapper e altre dipendenze al livello UI.
// Repository è anch'esso una Facade, ma a un livello inferiore
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 — una Facade per lo scenario di business
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 è una Facade per lo scenario di caricamento del profilo. Nasconde la logica di caching (local → remote), il mapping DTO → Entity → Profile e il tracciamento delle analisi. Il ViewModel chiama invoke(userId) e riceve un UserProfile pronto o un errore. UseCase può essere testato in isolamento, sostituendo il repository con un oggetto mock.
Facade in iOS è spesso implementato come Manager o Service. A differenza di Android, iOS usa i protocolli per definire l'interfaccia di Facade, il che consente di sostituire facilmente le implementazioni nei test. Consideriamo una Facade per lavorare con i media — caricamento, caching e visualizzazione.
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. Verificare la cache
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Caricare i dati
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Decodificare
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. Salvare nella cache
cache.setImage(image, for: url)
return .success(image)
}
}MediaService incapsula un processo in tre fasi: cache → caricamento → decodifica. La UI chiama un unico metodo loadImage(from:) invece di gestire ImageCache, URLSession e ImageDecoder. Durante i test, MediaServiceProtocol può essere sostituito con un mock che restituisce immagini predefinite senza caricamento reale.
Gli errori nella progettazione di Facade annullano i suoi vantaggi: invece di semplificazione, si ottiene un God Object da cui dipende l'intero sistema. Analizziamo i tre problemi principali.
Quando un'unica Facade contiene metodi per l'autenticazione, il caricamento del profilo, l'invio di messaggi e la sincronizzazione — questo è un God Object. Segnale: più di 15 metodi pubblici in una classe. Soluzione: dividerla in più Facade specializzate per aree di responsabilità — AuthService, ProfileService, MessagingService.
Se Facade restituisce tipi specifici del sottosistema (ad esempio, FirebaseUser o RealmObject), il client resta comunque legato a un'implementazione concreta. Soluzione: Facade deve restituire solo i propri tipi (data class / struct), astraendo completamente il client dai dettagli del sottosistema.
Quando Facade vieta l'accesso diretto al sottosistema, diventa un collo di bottiglia. A volte il client ha bisogno di un metodo specifico del sottosistema, e costringerlo a passare per Facade è ridondante. Facade non deve essere un gatekeeper rigido: fornisce un'interfaccia comoda, ma non blocca l'accesso diretto ai componenti.
Domande frequenti
Facade fornisce un'interfaccia semplificata al sottosistema, creando spesso un nuovo insieme di metodi. Proxy mantiene la stessa interfaccia dell'oggetto originale, ma aggiunge controllo degli accessi o caricamento lazy. Facade serve a semplificare, Proxy a controllare.
Service Layer è un'implementazione del pattern Facade a livello di architettura dell'applicazione. Definisce il confine tra UI e logica di business, nascondendo i dettagli di implementazione dei servizi. In Android, il Service Layer è spesso implementato tramite UseCase; in iOS — tramite i protocolli Manager o Service.
God Facade si verifica quando una classe si assume la responsabilità di più sottosistemi non correlati. Segnali: più di 15 metodi pubblici, metodi di domini diversi (autenticazione + pagamenti + notifiche), una classe difficile da testare (più di 10 dipendenze). Soluzione: dividere in Facade di dominio.
In un'applicazione con 1-2 schermate, Facade è ridondante — chiamare API e database direttamente dalla UI è più semplice e chiaro. Facade ripaga con 5+ schermate e 3+ sottosistemi. Nei progetti piccoli e medi è sufficiente un Repository come unico livello Facade, senza ulteriore wrapper UseCase.
Facade semplifica i test, poiché sostituisce un intero sottosistema con un unico oggetto mock. Invece di mockare tre componenti (rete + database + analisi), basta mockare una Facade. In Swift si usano i protocolli per questo; in Kotlin — le interfacce. Facade è utile anche per i test di integrazione, dove si verifica l'orchestrazione dei componenti.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche