软件设计

Swift Facade Pattern:用一个方法隐藏复杂子系统

Facade Pattern 为由多个对象交织而成的子系统提供一个简单的统一入口。本文以 Swift 用户注册为例,讲解如何集中管理调用顺序,以及它与 Adapter、Proxy、Decorator 的区别。

4 分钟阅读
Swift Facade Pattern:用一个方法隐藏复杂子系统 封面图

前段时间重新查看注册逻辑时,我忍不住叹了口气。

明明只点击了一个按钮,View Controller 却要直接调用六七个对象,分别处理校验、网络请求、Token 存储、通知注册等工作。

这套流程还要在其他页面复用,但我不想把整个调用顺序和组合原样复制过去。

这正是 Swift Facade Pattern 的用武之地。

先说结论,Facade Pattern 会把多个对象交织的复杂子系统封装到一个方法中,例如 signUp()。调用方不需要了解内部如何运行。

接口转换、访问控制和功能扩展之间的边界,可以在 Adapter、Facade、Proxy、Decorator 对比 中一眼看清。

今天我会用 Swift 代码讲解如何应用这个模式,也会结合自己的经验说明什么时候适合使用,以及什么时候反而会带来负担。

什么是 Facade Pattern?

Facade 原本指建筑物的“正面外观”。

从外面看,你只能看到整洁的建筑正面,但里面却布满了管道、电线和复杂设备。

软件也是如此。

当内部有多个类互相调用、共同运行的子系统时,就在它前面设置一个统一入口。

调用方只需要和这个入口交互。

核心就是“简化的入口”。

Facade 并不会消除复杂性,而是把复杂性集中在一个地方,只向外提供一扇简单的门。

关键在于隐藏内部实现,而不是删除它。

管道依然存在,只是藏到了墙后面。


用 Swift 直接实现是这样的

只听描述不容易理解,我们直接看代码。

假设注册流程包含三个子系统:校验、服务器注册和 Token 存储。

首先是我们想要隐藏的内部对象。

struct Validator { func check(_ email: String) -> Bool { email.contains("@") } }
struct AuthAPI { func register(_ email: String) -> String { "token_\(email)" } }
struct TokenStore { func save(_ token: String) { /* Keychain 存储 */ } }

如果由 View Controller 直接调用这三个对象,代码就会变得杂乱。

因此由 Facade 代替调用方协调它们。

struct SignUpFacade {
    private let validator = Validator()
    private let api = AuthAPI()
    private let store = TokenStore()

    func signUp(email: String) -> Bool {
        guard validator.check(email) else { return false }
        let token = api.register(email)   // 仅在这里管理内部顺序
        store.save(token)
        return true
    }
}

这样一来,调用方只需要一行代码。

就像这样:SignUpFacade().signUp(email: "[email protected]")

调用方完全不需要知道先校验、再保存 Token 的顺序。

一个 Facade 代替协调三个子系统的示意图
一个 Facade 代替协调三个子系统的示意图

以后即使顺序发生变化或增加一个步骤,也只需要修改 Facade 内部。


什么时候适合使用,什么时候应该避免?

下面是我亲自使用后总结出的判断标准。

适合使用 Facade 的情况

  • 多个对象总是按固定顺序调用,而且相同代码在各处重复时
  • 想封装外部库或复杂 SDK,并改成适合自己应用的简单名称时
  • View Controller 了解得太多,想为它减负时

更适合避免的情况

  • 子系统本来就很简单,几乎没有需要隐藏的内容时(只是平白增加一层)
  • 每次都需要细粒度控制,最终还是必须绕过 Facade 直接调用内部对象时

这里有一点我想特别强调。

Facade 并不是要“阻止”访问内部实现。

它只是提供一条更简单的路径;有需要时,仍然可以直接使用内部对象。

所以,它和那些试图控制所有访问的其他模式,性质并不相同。


几个常见问题

Q. Facade 和普通工具函数有什么区别?

工具函数通常只包含一个独立功能,而 Facade 的目标是协调多个对象之间的协作与顺序。

区别在于“隐藏的是什么”。

Q. 一定要做成协议吗?

不一定。

不过,如果你想在测试中用伪对象替换 Facade,先用协议进行抽象会方便得多。

Q. 我总是把它和 Adapter Pattern 搞混。

Adapter 的目的是让不匹配的接口彼此适配,而 Facade 的目的是用简单的方式呈现复杂内容。

记住两者的目的不同,就很容易理解。

这段示例代码,我在实际项目中就是这样整理的
这段示例代码,我在实际项目中就是这样整理的

总结

每次看到一大串复杂调用时,不妨问问自己:“能不能把它封装成一个方法?”

仅仅这个问题,就经常能让代码变得更容易阅读。

Facade 不是华丽的模式,却是实际开发中最常用、最可靠的工具之一。

建议你从今天的注册示例开始,轻松尝试一下。

只需打开一扇简洁的门,就是这种感觉
只需打开一扇简洁的门,就是这种感觉

延伸阅读