Facade — це структурний патерн проєктування, який надає спрощений інтерфейс до складної підсистеми класів. У мобільній розробці Facade найчастіше реалізується як Service Layer або UseCase, що приховує взаємодію з мережею, БД та аналітикою. За даними Martin Fowler (Patterns of Enterprise Application Architecture, 2003), Facade — один із ключових патернів для організації сервісного шару.
Головне
Facade — структурний патерн, який надає уніфікований інтерфейс до групи інтерфейсів підсистеми. Він визначає високорівневий інтерфейс, що спрощує використання підсистеми. Facade не додає нової функціональності — він оркеструє наявні компоненти, приховуючи складність їхньої взаємодії від клієнта.
// Складна підсистема
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 — простий інтерфейс для 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, що приховує AuthApi, UserDao та AnalyticsTracker від ViewModel. UI викликає loginUser(email, password) замість трьох окремих запитів до API, БД та аналітики. Це знижує зв'язність: якщо завтра AuthApi стане FirebaseAuth або UserDao мігрує на Room, зміниться лише Facade, а не UI.
Service Layer — поширена реалізація Facade в мобільних застосунках. Він інкапсулює бізнес-логіку та координацію між шарами. В Android Service Layer часто реалізується через UseCase (Clean Architecture), в iOS — через Manager або Service протоколи.
| Компонент | Роль у підсистемі | Що приховує Facade |
|---|---|---|
| AuthApi | Мережевий запит до сервера | Формат запиту, endpoint, обробку помилок HTTP |
| UserDao | Локальне зберігання токена | Схему БД, SQL-запити, міграції |
| AnalyticsTracker | Відправлення подій аналітики | Firebase/AppMetrica SDK, формат подій |
| NetworkMonitor | Перевірка доступності мережі | ConnectivityManager, BroadcastReceiver |
AuthService об'єднує всі чотири компоненти. ViewModel викликає один метод, не знаючи, що під капотом відбувається мережевий запит, запис у БД, трекінг та перевірка мережі. При тестуванні AuthService можна замінити mock'ом, перевіривши всю логіку авторизації без інтеграції з реальними компонентами.
Facade, Adapter і Mediator — структурні патерни, але вирішують різні завдання. Їх часто плутають, оскільки всі три вводять об'єкт-посередник. Розберемо відмінності на прикладі мобільного застосунку.
| Аспект | Facade | Adapter | Mediator |
|---|---|---|---|
| Мета | Спростити інтерфейс підсистеми | Перетворити інтерфейс | Зменшити зв'язність компонентів |
| Напрямок | Один інтерфейс → підсистема | Клієнт → Adaptee | N компонентів ↔ Mediator |
| Зміна інтерфейсу | Створює новий, спрощений | Перетворює наявний | Не змінює, координує |
| Чи знає підсистема про патерн? | Ні | Ні | Так, спілкуються через Mediator |
| Приклад у мобільній розробці | UseCase / Service Layer | RecyclerView.Adapter | Coodinator в iOS |
Facade не приховує підсистему — клієнт може за потреби звернутися до AuthApi напряму. Adapter в обов'язковому порядку змінює інтерфейс Adaptee. Mediator координує складні взаємодії між безліччю об'єктів, які можуть не знати одне про одного.
Реалізація Facade на Kotlin для Android з Clean Architecture використовує UseCase як точку входу для кожного бізнес-сценарію. UseCase — це Facade, що приховує репозиторій, мапер та інші залежності від UI-шару.
// Repository — теж Facade, але рівнем нижче
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 для бізнес-сценарію
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 для сценарію завантаження профілю. Він приховує логіку кешування (local → remote), маппінг DTO → Entity → Profile та трекінг аналітики. ViewModel викликає invoke(userId) і отримує готовий UserProfile або помилку. UseCase можна тестувати ізольовано, замінюючи репозиторій mock-об'єктом.
Facade в iOS часто реалізується як Manager або Service. На відміну від Android, iOS використовує протоколи для визначення інтерфейсу Facade, що дозволяє легко підміняти реалізації в тестах. Розглянемо Facade для роботи з медіа — завантаження, кешування та відображення.
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. Перевірити кеш
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Завантажити дані
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Декодувати
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. Зберегти в кеш
cache.setImage(image, for: url)
return .success(image)
}
}MediaService інкапсулює трикроковий процес: кеш → завантаження → декодування. UI викликає один метод loadImage(from:) замість керування ImageCache, URLSession та ImageDecoder. При тестуванні можна підмінити MediaServiceProtocol mock'ом, що повертає попередньо встановлені зображення без реального завантаження.
Помилки при проєктуванні Facade зводять нанівець його переваги: замість спрощення виходить God Object, від якого залежить вся система. Розберемо три головні проблеми.
Коли один Facade містить методи для авторизації, завантаження профілю, відправлення повідомлень та синхронізації — це God Object. Ознака: понад 15 публічних методів в одному класі. Рішення: розділити на кілька спеціалізованих Facade за сферами відповідальності — AuthService, ProfileService, MessagingService.
Якщо Facade повертає типи, специфічні для підсистеми (наприклад, FirebaseUser або RealmObject), клієнт усе одно прив'язаний до конкретної реалізації. Рішення: Facade має повертати лише власні типи (data class / struct), повністю абстрагуючи клієнта від деталей підсистеми.
Коли Facade забороняє прямий доступ до підсистеми, він стає bottleneck. Іноді клієнту потрібен специфічний метод підсистеми, і змушувати його проходити через Facade — зайво. Facade не має бути суворим gatekeeper: він надає зручний інтерфейс, але не блокує прямий доступ до компонентів.
Часті запитання
Facade надає спрощений інтерфейс до підсистеми, часто створюючи новий набір методів. Proxy зберігає той самий інтерфейс, що й оригінальний об'єкт, але додає контроль доступу або ліниве завантаження. Facade — для спрощення, Proxy — для контролю.
Service Layer — це реалізація патерну Facade на рівні архітектури застосунку. Він визначає межу між UI та бізнес-логікою, приховуючи деталі реалізації сервісів. В Android Service Layer часто реалізується через UseCase, в iOS — через Manager або Service протоколи.
God Facade виникає, коли один клас бере на себе відповідальність за кілька незв'язаних підсистем. Ознаки: понад 15 публічних методів, методи з різних доменів (авторизація + платежі + сповіщення), клас, який складно тестувати (понад 10 залежностей). Рішення: розділити на доменні Facade.
У застосунку з 1-2 екранами Facade зайвий — прямий виклик API та БД з UI простіший і зрозуміліший. Facade окуповується при 5+ екранах і 3+ підсистемах. У середніх і малих проєктах достатньо Repository як єдиного Facade-шару, без додаткової обгортки UseCase.
Facade спрощує тестування, оскільки замінює цілу підсистему одним mock-об'єктом. Замість mock трьох компонентів (мережа + БД + аналітика) достатньо mock одного Facade. У Swift для цього використовують protocol, у Kotlin — interface. Facade також зручний для інтеграційних тестів, де перевіряється оркестрація компонентів.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також