Facade — to strukturalny wzorzec projektowy, który zapewnia uproszczony interfejs do złożonego podsystemu klas. W rozwoju aplikacji mobilnych Facade najczęściej jest implementowany jako Service Layer lub UseCase, ukrywający współpracę z siecią, bazą danych i analityką. Według Martina Fowlera (Patterns of Enterprise Application Architecture, 2003), Facade jest jednym z kluczowych wzorców organizacji warstwy usług.
Najważniejsze
Facade — strukturalny wzorzec, który zapewnia ujednolicony interfejs do grupy interfejsów podsystemu. Definiuje interfejs wysokiego poziomu, upraszczający korzystanie z podsystemu. Facade nie dodaje nowej funkcjonalności — orkiestruje istniejące komponenty, ukrywając przed klientem złożoność ich wzajemnych interakcji.
// Złożony podsystem
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 — prosty interfejs dla 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 ukrywający AuthApi, UserDao i AnalyticsTracker przed ViewModel. UI wywołuje loginUser(email, password) zamiast trzech osobnych żądań do API, bazy danych i analityki. Zmniejsza to sprzężenie: jeśli jutro AuthApi stanie się FirebaseAuth, a UserDao zmigruje do Room, zmieni się tylko Facade, a nie UI.
Service Layer — popularna implementacja Facade w aplikacjach mobilnych. Enkapsuluje logikę biznesową i koordynację między warstwami. W Androidzie Service Layer często jest implementowany przez UseCase (Clean Architecture), w iOS — przez Manager lub protokoły Service.
| Komponent | Rola w podsystemie | Co ukrywa Facade |
|---|---|---|
| AuthApi | Żądanie sieciowe do serwera | Format żądania, endpoint, obsługę błędów HTTP |
| UserDao | Lokalne przechowywanie tokena | Schemat bazy danych, zapytania SQL, migracje |
| AnalyticsTracker | Wysyłanie zdarzeń analitycznych | SDK Firebase/AppMetrica, format zdarzeń |
| NetworkMonitor | Sprawdzanie dostępności sieci | ConnectivityManager, BroadcastReceiver |
AuthService łączy wszystkie cztery komponenty. ViewModel wywołuje jedną metodę, nie wiedząc, że pod spodem odbywa się żądanie sieciowe, zapis do bazy, śledzenie i sprawdzanie sieci. Podczas testów AuthService można zastąpić mockiem, weryfikując całą logikę autoryzacji bez integracji z prawdziwymi komponentami.
Facade, Adapter i Mediator — wzorce strukturalne, ale rozwiązują różne zadania. Często są mylone, ponieważ wszystkie trzy wprowadzają obiekt pośredniczący. Przeanalizujmy różnice na przykładzie aplikacji mobilnej.
| Aspekt | Facade | Adapter | Mediator |
|---|---|---|---|
| Cel | Uprościć interfejs podsystemu | Przekształcić interfejs | Zmniejszyć sprzężenie komponentów |
| Kierunek | Jeden interfejs → podsystem | Klient → Adaptee | N komponentów ↔ Mediator |
| Zmiana interfejsu | Tworzy nowy, uproszczony | Przekształca istniejący | Nie zmienia, koordynuje |
| Czy podsystem zna wzorzec? | Nie | Nie | Tak, komunikuje się przez Mediator |
| Przykład w rozwoju mobilnym | UseCase / Service Layer | RecyclerView.Adapter | Coordinator w iOS |
Facade nie ukrywa podsystemu — klient może w razie potrzeby uzyskać bezpośredni dostęp do AuthApi. Adapter obligatoryjnie zmienia interfejs Adaptee. Mediator koordynuje złożone interakcje między wieloma obiektami, które mogą się nie znać nawzajem.
Implementacja Facade w Kotlin dla Androida z Clean Architecture wykorzystuje UseCase jako punkt wejścia dla każdego scenariusza biznesowego. UseCase — to Facade ukrywający repozytorium, mapper i inne zależności przed warstwą UI.
// Repository — też Facade, ale o poziom niżej
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 dla scenariusza biznesowego
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 dla scenariusza ładowania profilu. Ukrywa logikę buforowania (local → remote), mapowanie DTO → Entity → Profile i śledzenie analityki. ViewModel wywołuje invoke(userId) i otrzymuje gotowy UserProfile lub błąd. UseCase można testować w izolacji, zastępując repozytorium obiektem mock.
Facade w iOS często jest implementowany jako Manager lub Service. W przeciwieństwie do Androida, iOS używa protokołów do definiowania interfejsu Facade, co ułatwia podmianę implementacji w testach. Rozważmy Facade do pracy z mediami — ładowanie, buforowanie i wyświetlanie.
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. Sprawdzić pamięć podręczną
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Pobrać dane
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Zdekodować
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. Zapisać w pamięci podręcznej
cache.setImage(image, for: url)
return .success(image)
}
}MediaService enkapsuluje proces trzykrokowy: pamięć podręczna → ładowanie → dekodowanie. UI wywołuje jedną metodę loadImage(from:) zamiast zarządzania ImageCache, URLSession i ImageDecoder. Podczas testów można podmienić MediaServiceProtocol mockiem zwracającym wcześniej przygotowane obrazy bez prawdziwego ładowania.
Błędy w projektowaniu Facade niwelują jego zalety: zamiast uproszczenia powstaje God Object, od którego zależy cały system. Przeanalizujmy trzy główne problemy.
Kiedy jeden Facade zawiera metody autoryzacji, ładowania profilu, wysyłania wiadomości i synchronizacji — to God Object. Oznaka: 15+ metod publicznych w jednej klasie. Rozwiązanie: podzielić na kilka wyspecjalizowanych Facade według obszarów odpowiedzialności — AuthService, ProfileService, MessagingService.
Jeśli Facade zwraca typy specyficzne dla podsystemu (np. FirebaseUser lub RealmObject), klient i tak jest związany z konkretną implementacją. Rozwiązanie: Facade powinien zwracać tylko własne typy (data class / struct), całkowicie abstrahując klienta od szczegółów podsystemu.
Kiedy Facade zabrania bezpośredniego dostępu do podsystemu, staje się wąskim gardłem. Czasami klient potrzebuje specyficznej metody podsystemu i zmuszanie go do przechodzenia przez Facade jest zbędne. Facade nie powinien być ścisłym gatekeeperem: zapewnia wygodny interfejs, ale nie blokuje bezpośredniego dostępu do komponentów.
Często zadawane pytania
Facade zapewnia uproszczony interfejs do podsystemu, często tworząc nowy zestaw metod. Proxy zachowuje ten sam interfejs, co oryginalny obiekt, ale dodaje kontrolę dostępu lub leniwe ładowanie. Facade — dla uproszczenia, Proxy — dla kontroli.
Service Layer — to implementacja wzorca Facade na poziomie architektury aplikacji. Definiuje granicę między UI a logiką biznesową, ukrywając szczegóły implementacji usług. W Androidzie Service Layer często jest implementowany przez UseCase, w iOS — przez Manager lub protokoły Service.
God Facade powstaje, gdy jedna klasa bierze odpowiedzialność za kilka niepowiązanych podsystemów. Oznaki: 15+ metod publicznych, metody z różnych domen (autoryzacja + płatności + powiadomienia), klasa trudna do testowania (10+ zależności). Rozwiązanie: podzielić na domenowe Facade.
W aplikacji z 1-2 ekranami Facade jest zbędny — bezpośrednie wywołanie API i bazy danych z UI jest prostsze i bardziej zrozumiałe. Facade się opłaca przy 5+ ekranach i 3+ podsystemach. W średnich i małych projektach wystarczy Repository jako jedyna warstwa Facade, bez dodatkowej otoczki UseCase.
Facade upraszcza testowanie, ponieważ zastępuje cały podsystem jednym obiektem mock. Zamiast mockowania trzech komponentów (sieć + baza + analityka) wystarczy mock jednego Facade. W Swift do tego używa się protocol, w Kotlin — interface. Facade jest też wygodny w testach integracyjnych, gdzie sprawdza się orkiestrację komponentów.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również