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 | Coordinator в 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 забранява директния достъп до подсистемата, той се превръща в тясно място. Понякога клиентът се нуждае от специфичен метод на подсистемата и да го принуждаваме да минава през Facade е излишно. Facade не трябва да бъде строг пазач: той предоставя удобен интерфейс, но не блокира директния достъп до компонентите.
Често задавани въпроси
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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също