Facade is een structureel ontwerppatroon dat een vereenvoudigde interface biedt naar een complex subsysteem van klassen. In mobiele ontwikkeling wordt Facade meestal geïmplementeerd als Service Layer of UseCase, die de interactie met het netwerk, de database en de analytics verbergt. Volgens Martin Fowler (Patterns of Enterprise Application Architecture, 2003) is Facade een van de sleutelpatronen voor het organiseren van de servicelaag.
Belangrijkste punten
Facade — structureel patroon dat een uniforme interface biedt naar een groep interfaces van het subsysteem. Het definieert een interface op hoog niveau die het gebruik van het subsysteem vereenvoudigt. Facade voegt geen nieuwe functionaliteit toe — het orkestreert bestaande componenten en verbergt de complexiteit van hun interactie voor de client.
// Complex subsysteem
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 — eenvoudige interface voor 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 — Facade die AuthApi, UserDao en AnalyticsTracker voor ViewModel verbergt. UI roept loginUser(email, password) aan in plaats van drie afzonderlijke verzoeken naar API, database en analytics. Dit vermindert de koppeling: als AuthApi morgen FirebaseAuth wordt of UserDao migreert naar Room, verandert alleen Facade, niet UI.
Service Layer — veelvoorkomende implementatie van Facade in mobiele applicaties. Het encapsuleert de businesslogica en de coördinatie tussen lagen. In Android wordt Service Layer vaak geïmplementeerd via UseCase (Clean Architecture), in iOS — via Manager of Service-protocollen.
| Component | Rol in het subsysteem | Wat Facade verbergt |
|---|---|---|
| AuthApi | Netwerkverzoek naar server | Verzoekformaat, endpoint, HTTP-foutafhandeling |
| UserDao | Lokale opslag van token | Database-schema, SQL-query's, migraties |
| AnalyticsTracker | Versturen van analytics-gebeurtenissen | Firebase/AppMetrica SDK, gebeurtenisformaat |
| NetworkMonitor | Controleren van netwerkbeschikbaarheid | ConnectivityManager, BroadcastReceiver |
AuthService verenigt alle vier componenten. ViewModel roept één methode aan, zonder te weten dat er onder de motorkap een netwerkverzoek, een schrijf naar de database, tracking en een netwerkcontrole plaatsvinden. Bij het testen kan AuthService worden vervangen door een mock, waarmee de hele authenticatielogica wordt gecontroleerd zonder integratie met echte componenten.
Facade, Adapter en Mediator — structurele patronen, maar ze lossen verschillende problemen op. Ze worden vaak verward, omdat alle drie een tussenliggend object introduceren. Laten we de verschillen bekijken aan de hand van een mobiele applicatie.
| Aspect | Facade | Adapter | Mediator |
|---|---|---|---|
| Doel | Interface van subsysteem vereenvoudigen | Interface omzetten | Koppeling van componenten verminderen |
| Richting | Eén interface → subsysteem | Client → Adaptee | N componenten ↔ Mediator |
| Verandering van interface | Creëert een nieuwe, vereenvoudigde | Zet de bestaande om | Verandert niet, coördineert |
| Weet het subsysteem van het patroon? | Nee | Nee | Ja, communiceert via Mediator |
| Voorbeeld in mobiele ontwikkeling | UseCase / Service Layer | RecyclerView.Adapter | Coordinator in iOS |
Facade verbergt het subsysteem niet — de client kan indien nodig rechtstreeks toegang krijgen tot AuthApi. Adapter verandert verplicht de interface van Adaptee. Mediator coördineert complexe interacties tussen vele objecten die elkaar misschien niet kennen.
De implementatie van Facade in Kotlin voor Android met Clean Architecture gebruikt UseCase als ingangspunt voor elk businessscenario. UseCase is een Facade die de repository, mapper en andere afhankelijkheden voor de UI-laag verbergt.
// Repository — ook Facade, maar één niveau lager
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 — Facade voor het businessscenario
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 — Facade voor het scenario van het laden van een profiel. Het verbergt de cachinglogica (local → remote), de mapping DTO → Entity → Profile en analytics-tracking. ViewModel roept invoke(userId) aan en krijgt een kant-en-klaar UserProfile of een foutmelding. UseCase kan geïsoleerd worden getest door de repository te vervangen door een mock-object.
Facade in iOS wordt vaak geïmplementeerd als Manager of Service. In tegenstelling tot Android gebruikt iOS protocollen om de interface van Facade te definiëren, waardoor implementaties gemakkelijk kunnen worden uitgewisseld in tests. Laten we een Facade bekijken voor het werken met media — laden, cachen en weergeven.
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. Cache controleren
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Gegevens laden
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Decoderen
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. In cache opslaan
cache.setImage(image, for: url)
return .success(image)
}
}MediaService encapsuleert een proces in drie stappen: cache → laden → decoderen. UI roept één methode loadImage(from:) aan in plaats van ImageCache, URLSession en ImageDecoder te beheren. Bij het testen kan MediaServiceProtocol worden vervangen door een mock die vooraf ingestelde afbeeldingen retourneert zonder echt laden.
Fouten bij het ontwerpen van Facade tenietdoen de voordelen ervan: in plaats van vereenvoudiging ontstaat een God Object waarvan het hele systeem afhankelijk is. Laten we drie hoofproblemen bekijken.
Wanneer één Facade methoden bevat voor authenticatie, het laden van profielen, het versturen van berichten en synchronisatie — is dit een God Object. Kenmerk: 15+ publieke methoden in één klasse. Oplossing: opsplitsen in meerdere gespecialiseerde Facade's per verantwoordelijkheidsgebied — AuthService, ProfileService, MessagingService.
Als Facade types retourneert die specifiek zijn voor het subsysteem (bijvoorbeeld FirebaseUser of RealmObject), blijft de client toch gebonden aan een concrete implementatie. Oplossing: Facade moet alleen eigen types retourneren (data class / struct), waardoor de client volledig wordt geabstraheerd van de details van het subsysteem.
Wanneer Facade directe toegang tot het subsysteem verbiedt, wordt het een bottleneck. Soms heeft de client een specifieke methode van het subsysteem nodig en is het dwingen om via Facade te gaan overbodig. Facade hoeft geen strikte gatekeeper te zijn: het biedt een handige interface, maar blokkeert geen directe toegang tot componenten.
Veelgestelde vragen
Facade biedt een vereenvoudigde interface naar het subsysteem en creëert vaak een nieuwe set methoden. Proxy behoudt dezelfde interface als het originele object, maar voegt toegangscontrole of lazy loading toe. Facade — voor vereenvoudiging, Proxy — voor controle.
Service Layer — dit is de implementatie van het Facade-patroon op het niveau van de applicatie-architectuur. Het definieert de grens tussen UI en businesslogica en verbergt de implementatiedetails van services. In Android wordt Service Layer vaak geïmplementeerd via UseCase, in iOS — via Manager of Service-protocollen.
God Facade ontstaat wanneer één klasse de verantwoordelijkheid voor meerdere niet-gerelateerde subsystemen op zich neemt. Kenmerken: 15+ publieke methoden, methoden uit verschillende domeinen (authenticatie + betalingen + meldingen), een klasse die moeilijk te testen is (10+ afhankelijkheden). Oplossing: opsplitsen in domein-Facade's.
In een applicatie met 1-2 schermen is Facade overbodig — directe aanroep van API en database vanuit UI is eenvoudiger en duidelijker. Facade betaalt zich terug bij 5+ schermen en 3+ subsystemen. In middelgrote en kleine projecten is Repository voldoende als enige Facade-laag, zonder extra UseCase-wrapper.
Facade vereenvoudigt testen, omdat het het hele subsysteem vervangt door één mock-object. In plaats van drie componenten te mocken (netwerk + database + analytics) is het mocken van één Facade voldoende. In Swift wordt hiervoor protocol gebruikt, in Kotlin — interface. Facade is ook handig voor integratietests, waarin de orkestratie van componenten wordt gecontroleerd.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook