讲解
家里的「一键观影」模式:按一下遥控器,投影降下来、灯光调暗、功放切到 HDMI、播放器开机——你不用知道这四台设备各自的遥控器在哪。外观模式就是这个「一键」按钮:为一个复杂子系统提供一个统一的高层入口,让客户端用一次调用完成原本要协调一堆对象才能做完的事。
GoF 的定义:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。注意它「不封死」子系统——懂行的用户仍然可以绕过外观直接操作底层设备(类),外观只是给最常见、最标准的用法铺一条捷径。
外观是 23 种模式里结构最简单、实践中出现频率却极高的一种,只是大家经常不这么叫它。SDK 的顶层入口(new SomeClient() 背后协调鉴权、重试、序列化、传输四个模块)、Node.js 的 fs.readFile 之于底层系统调用、ORM 的 save() 之于 SQL 构造与连接管理,本质都是外观。它和「分层架构」里的「服务层」概念也高度重叠:Controller 不调五个 Repository,而是调一个 Service 外观。
和适配器再分一次工:适配器改变接口(把方的翻译成圆的),外观简化接口(把五步收成一步,接口形状不变只是更友好)。外观和中介者也容易混:外观是单向的(客户端 → 子系统),中介者是多向协调(同事对象互相通信都经过它),中介者一章会展开。
示例
「一键开机」外观:把 CPU 自检、内存加载、磁盘读取、进入桌面这串固定顺序的调用收进 ComputerFacade.boot():
import assert from 'node:assert/strict';
// 子系统:启动一台电脑要协调的一堆零件(各自有自己的接口和顺序要求)
class Cpu {
start(): string {
return 'CPU 自检通过';
}
}
class Memory {
load(): string {
return '内存加载引导程序';
}
}
class Disk {
readBoot(): string {
return '磁盘读取操作系统';
}
}
// 外观:把固定顺序的子系统调用收进一个方法
class ComputerFacade {
private readonly cpu = new Cpu();
private readonly memory = new Memory();
private readonly disk = new Disk();
boot(): string[] {
return [this.cpu.start(), this.memory.load(), this.disk.readBoot(), '启动完成,进入桌面'];
}
}
// 客户端一行搞定,不需要知道零件和顺序
const steps = new ComputerFacade().boot();
assert.deepEqual(steps, ['CPU 自检通过', '内存加载引导程序', '磁盘读取操作系统', '启动完成,进入桌面']);
console.log(steps.join(' -> '));
收益不只是一行代码:启动顺序这个知识(先 CPU 再内存再磁盘)本来散落在每个调用方的脑子里,现在被固化在 ComputerFacade 里,成为唯一权威来源。哪天启动流程加一步「网络初始化」,只有外观需要改;而需要精细控制的高级用户,仍然可以直接 new Cpu() 自己编排——外观是捷径,不是围墙。
常见坑
- 外观变成「上帝类」:什么都往里塞,外观自己膨胀成新的复杂子系统;一个外观只服务一类高频场景,场景多了拆成多个外观。
- 用外观封死底层:把子系统类藏起来不让外部碰,会逼得有特殊需求的用户去 hack;外观应提供便利而非强制。
- 外观里出现新业务规则:外观只做编排(先谁后谁),业务判断属于子系统内部;外观越「薄」越好。
- 一个外观直接调十个模块:那是把「高耦合」换了个地方。模块多时先分层(子系统内部再聚合),外观面向聚合后的少量入口。
- 以为有了外观就不用文档:外观覆盖的是 80% 标准路径,剩下 20% 的精细用法需要子系统文档兜底。
小结
外观为复杂子系统提供统一高层入口,把编排知识固化在一处,是 SDK 入口、服务层的本质形态;它提供便利而不封死底层。下一章讲用共享消灭内存浪费的享元模式。