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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также