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,向 ViewModel 隐藏 AuthApi、UserDao 和 AnalyticsTracker。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 | 本地存储 token | 数据库模式、SQL 查询、迁移 |
| AnalyticsTracker | 发送分析事件 | Firebase/AppMetrica SDK、事件格式 |
| NetworkMonitor | 检查网络可用性 | ConnectivityManager、BroadcastReceiver |
AuthService 整合了所有四个组件。ViewModel 调用一个方法,而不知道在底层发生了网络请求、数据库写入、跟踪和网络检查。在测试时,可以用 mock 替换 AuthService,无需与真实组件集成即可检查整个认证逻辑。
Facade、Adapter 和 Mediator — 都是结构型模式,但解决不同的问题。它们经常被混淆,因为三者都引入了一个中介对象。我们以移动应用为例分析区别。
| 方面 | Facade | Adapter | Mediator |
|---|---|---|---|
| 目标 | 简化子系统接口 | 转换接口 | 降低组件耦合 |
| 方向 | 一个接口 → 子系统 | 客户端 → Adaptee | N 个组件 ↔ Mediator |
| 接口的变化 | 创建新的、简化的接口 | 转换现有接口 | 不改变,进行协调 |
| 子系统知道这个模式吗? | 不知道 | 不知道 | 知道,通过 Mediator 通信 |
| 移动开发中的示例 | UseCase / Service Layer | RecyclerView.Adapter | iOS 中的 Coordinator |
Facade 不隐藏子系统 — 客户端在需要时可以直接访问 AuthApi。Adapter 必须改变 Adaptee 的接口。Mediator 协调许多可能互不了解的对象之间的复杂交互。
Facade 的实现 在 Kotlin 中为 Android 使用 Clean Architecture,将 UseCase 作为每个业务场景的入口点。UseCase 是一个 Facade,向 UI 层隐藏 repository、mapper 和其他依赖。
// 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 或错误。可以用 mock 对象替换 repository,从而对 UseCase 进行隔离测试。
iOS 中的 Facade 通常以 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。在测试时,可以用 mock 替换 MediaServiceProtocol,无需真实加载即可返回预设图像。
错误 在设计 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 是多余的 — 从 UI 直接调用 API 和数据库更简单、更清晰。Facade 值得使用 当有 5 个以上屏幕和 3 个以上子系统时。在中小型项目中,Repository 作为唯一的 Facade 层就足够了,无需额外的 UseCase 包装。
Facade 简化了测试,因为它用一个 mock 对象替换了整个子系统。与其 mock 三个组件(网络 + 数据库 + 分析),不如只 mock 一个 Facade。在 Swift 中使用 protocol,在 Kotlin 中使用 interface。Facade 也适用于集成测试,用于检查组件的编排。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。