Facade is a structural design pattern that provides a simplified interface to a complex subsystem of classes. In mobile development, Facade is most often implemented as a Service Layer or UseCase, hiding interactions with the network, database, and analytics. According to Martin Fowler (Patterns of Enterprise Application Architecture, 2003), Facade is one of the key patterns for organizing a service layer.
Key Takeaways
Facade is a structural pattern that provides a unified interface to a group of subsystem interfaces. It defines a high-level interface that simplifies the use of the subsystem. Facade does not add new functionality — it orchestrates existing components, hiding the complexity of their interaction from the client.
// Complex subsystem
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 — a simple interface for the 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 is a Facade that hides AuthApi, UserDao, and AnalyticsTracker from the ViewModel. The UI calls loginUser(email, password) instead of three separate requests to the API, database, and analytics. This reduces coupling: if tomorrow AuthApi becomes FirebaseAuth or UserDao migrates to Room, only the Facade changes, not the UI.
Service Layer is a common Facade implementation in mobile applications. It encapsulates business logic and coordination between layers. In Android, the Service Layer is often implemented via UseCase (Clean Architecture), and in iOS via Manager or Service protocols.
| Component | Role in the subsystem | What the Facade hides |
|---|---|---|
| AuthApi | Network request to the server | Request format, endpoint, HTTP error handling |
| UserDao | Local token storage | Database schema, SQL queries, migrations |
| AnalyticsTracker | Sending analytics events | Firebase/AppMetrica SDK, event format |
| NetworkMonitor | Network availability check | ConnectivityManager, BroadcastReceiver |
AuthService combines all four components. The ViewModel calls one method without knowing that under the hood a network request, database write, tracking, and network check are happening. When testing, AuthService can be replaced with a mock to verify the entire authentication logic without integrating with real components.
Facade, Adapter, and Mediator are structural patterns, but they solve different problems. They are often confused because all three introduce a mediator object. Let's look at the differences using a mobile application example.
| Aspect | Facade | Adapter | Mediator |
|---|---|---|---|
| Purpose | Simplify the subsystem interface | Convert an interface | Reduce component coupling |
| Direction | One interface → subsystem | Client → Adaptee | N components ↔ Mediator |
| Interface change | Creates a new, simplified one | Converts the existing one | Does not change, coordinates |
| Does the subsystem know about the pattern? | No | No | Yes, communicates through Mediator |
| Example in mobile development | UseCase / Service Layer | RecyclerView.Adapter | Coordinator in iOS |
Facade does not hide the subsystem — the client can access AuthApi directly when needed. Adapter necessarily changes the Adaptee's interface. Mediator coordinates complex interactions between many objects that may not know about each other.
Facade implementation in Kotlin for Android with Clean Architecture uses UseCase as an entry point for each business scenario. UseCase is a Facade that hides the repository, mapper, and other dependencies from the UI layer.
// Repository is also a Facade, but at a lower level
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 — a Facade for a business scenario
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 is a Facade for the profile loading scenario. It hides caching logic (local → remote), DTO → Entity → Profile mapping, and analytics tracking. The ViewModel calls invoke(userId) and receives a ready UserProfile or an error. UseCase can be tested in isolation by replacing the repository with a mock object.
Facade in iOS is often implemented as a Manager or Service. Unlike Android, iOS uses protocols to define the Facade interface, making it easy to swap implementations in tests. Let's look at a Facade for working with media — loading, caching, and display.
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. Check the cache
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. Load data
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. Decode
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. Save to the cache
cache.setImage(image, for: url)
return .success(image)
}
}MediaService encapsulates a three-step process: cache → load → decode. The UI calls one method loadImage(from:) instead of managing ImageCache, URLSession, and ImageDecoder. When testing, MediaServiceProtocol can be replaced with a mock returning preset images without real loading.
Mistakes in Facade design negate its advantages: instead of simplification, you get a God Object that the whole system depends on. Let's look at the three main problems.
When a single Facade contains methods for authorization, profile loading, message sending, and synchronization — this is a God Object. A sign: 15+ public methods in one class. Solution: split it into several specialized Facades by area of responsibility — AuthService, ProfileService, MessagingService.
If a Facade returns types specific to the subsystem (for example, FirebaseUser or RealmObject), the client is still tied to a specific implementation. Solution: the Facade should return only its own types (data class / struct), completely abstracting the client from subsystem details.
When a Facade forbids direct access to the subsystem, it becomes a bottleneck. Sometimes the client needs a specific subsystem method, and forcing them through the Facade is redundant. Facade should not be a strict gatekeeper: it provides a convenient interface but does not block direct access to components.
Frequently Asked Questions
Facade provides a simplified interface to the subsystem, often creating a new set of methods. Proxy keeps the same interface as the original object but adds access control or lazy loading. Facade is for simplification, Proxy is for control.
Service Layer is a Facade pattern implementation at the application architecture level. It defines the boundary between UI and business logic, hiding service implementation details. In Android, the Service Layer is often implemented via UseCase; in iOS, via Manager or Service protocols.
God Facade occurs when one class takes responsibility for several unrelated subsystems. Signs: 15+ public methods, methods from different domains (authorization + payments + notifications), a class that is hard to test (10+ dependencies). Solution: split into domain Facades.
In an app with 1-2 screens, Facade is redundant — calling the API and database directly from the UI is simpler and clearer. Facade pays off with 5+ screens and 3+ subsystems. In small and medium projects, a Repository as the only Facade layer is enough, without an extra UseCase wrapper.
Facade simplifies testing because it replaces an entire subsystem with a single mock object. Instead of mocking three components (network + database + analytics), mocking one Facade is enough. In Swift, protocols are used for this; in Kotlin, interfaces. Facade is also convenient for integration tests that verify component orchestration.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also