Facade:移动架构中的外观模式基础

作者: IT Sectr 发布日期: 2026-02-18 阅读时间: 9 分钟

Facade 是一种结构型设计模式,为复杂的类子系统提供简化的接口。在移动开发中,Facade 通常以 Service Layer 或 UseCase 的形式实现,隐藏与网络、数据库和分析工具的交互。根据 Martin Fowler(Patterns of Enterprise Application Architecture, 2003)的说法,Facade 是组织服务层的关键模式之一。

要点

  • Facade — 结构型模式,为类、库或框架组成的复杂系统提供简单接口
  • Service Layer — Facade 在移动架构中的实现,对 UI 隐藏 API、缓存和分析
  • Facade 不隐藏子系统 — 客户端在需要时可以直接访问子系统
  • Clean Architecture 中的 UseCase — Facade 的一种变体,编排一个业务场景
  • Facade 与 Adapter:Facade 简化接口,Adapter 将一个接口转换为另一个接口

什么是 Facade 模式?

Facade — 结构型模式,为一组子系统接口提供统一接口。它定义了简化子系统使用的高层接口。Facade 不添加新功能 — 它编排现有组件,向客户端隐藏它们交互的复杂性。

Kotlin
// 复杂的子系统
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 不会。

移动架构中的 Facade:Service Layer

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 — 都是结构型模式,但解决不同的问题。它们经常被混淆,因为三者都引入了一个中介对象。我们以移动应用为例分析区别。

方面FacadeAdapterMediator
目标简化子系统接口转换接口降低组件耦合
方向一个接口 → 子系统客户端 → AdapteeN 个组件 ↔ Mediator
接口的变化创建新的、简化的接口转换现有接口不改变,进行协调
子系统知道这个模式吗?不知道不知道知道,通过 Mediator 通信
移动开发中的示例UseCase / Service LayerRecyclerView.AdapteriOS 中的 Coordinator

Facade 不隐藏子系统 — 客户端在需要时可以直接访问 AuthApi。Adapter 必须改变 Adaptee 的接口。Mediator 协调许多可能互不了解的对象之间的复杂交互。

在 Kotlin 中为 Android 实现 Facade

Facade 的实现 在 Kotlin 中为 Android 使用 Clean Architecture,将 UseCase 作为每个业务场景的入口点。UseCase 是一个 Facade,向 UI 层隐藏 repository、mapper 和其他依赖。

Kotlin
// 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 进行隔离测试。

在 Swift 中为 iOS 实现 Facade

iOS 中的 Facade 通常以 Manager 或 Service 的形式实现。与 Android 不同,iOS 使用协议来定义 Facade 的接口,这使得在测试中可以轻松替换实现。我们看一个用于处理媒体的 Facade — 加载、缓存和显示。

Swift
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 时的典型错误

错误 在设计 Facade 时会使它的优点化为乌有:不是简化,而是出现了整个系统都依赖的 God Object。我们分析三个主要问题。

God Facade — 责任过多

当一个 Facade 包含用于认证、加载资料、发送消息和同步的方法时 — 这就是 God Object。标志:一个类中有超过 15 个公共方法。解决方案:按职责领域拆分为多个专门化的 Facade — AuthService、ProfileService、MessagingService。

泄露子系统细节的 Facade

如果 Facade 返回子系统特有的类型(例如 FirebaseUserRealmObject),客户端仍然绑定到特定的实现。解决方案:Facade 应该只返回自己的类型(data class / struct),使客户端完全摆脱对子系统细节的依赖。

Facade 作为唯一的入口点

当 Facade 禁止直接访问子系统时,它就变成了瓶颈。有时客户端需要子系统的特定方法,强迫它经过 Facade 是多余的。Facade 不应该是严格的守门人:它提供方便的接口,但不阻止对组件的直接访问。

常见问题

Facade 和 Proxy 有什么区别?

Facade 为子系统提供简化的接口,通常创建一组新方法。Proxy 保持与原始对象相同的接口,但添加访问控制或惰性加载。Facade — 用于简化,Proxy — 用于控制。

Facade 和 Service Layer 是同一回事吗?

Service Layer — 是 Facade 模式在应用程序架构层面的实现。它定义了 UI 和业务逻辑之间的边界,隐藏服务实现的细节。在 Android 中,Service Layer 通常通过 UseCase 实现,在 iOS 中 — 通过 Manager 或 Service 协议实现。

Facade 什么时候会变成 God Object?

God Facade 出现在一个类承担多个不相关子系统的责任时。标志:超过 15 个公共方法,来自不同领域的方法(认证 + 支付 + 通知),难以测试的类(超过 10 个依赖)。解决方案:拆分为领域化的 Facade。

小型应用需要 Facade 吗?

在只有 1-2 个屏幕的应用中,Facade 是多余的 — 从 UI 直接调用 API 和数据库更简单、更清晰。Facade 值得使用 当有 5 个以上屏幕和 3 个以上子系统时。在中小型项目中,Repository 作为唯一的 Facade 层就足够了,无需额外的 UseCase 包装。

如何测试使用 Facade 的代码?

Facade 简化了测试,因为它用一个 mock 对象替换了整个子系统。与其 mock 三个组件(网络 + 数据库 + 分析),不如只 mock 一个 Facade。在 Swift 中使用 protocol,在 Kotlin 中使用 interface。Facade 也适用于集成测试,用于检查组件的编排。

总结

  • Facade — 结构型模式,为复杂子系统提供简单接口
  • Service LayerUseCase — Facade 在移动架构中的常见实现
  • Facade 不隐藏子系统:客户端在需要时可以自由访问组件
  • Facade 与 Adapter:Facade 简化,Adapter 转换;Facade 与 Mediator:Facade 是单向的,Mediator 是双向的
  • God Facade — 反模式:一个类中超过 15 个方法表明违反了单一职责原则
  • 协议/接口 对 Facade 是必需的 — 这是对子系统进行 mock 测试的唯一方式
  • 建议:在 5 个以上屏幕和 3 个以上子系统时引入 Facade;小项目使用 Repository 就足够了

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读