外观模式
1. 核心概念与价值
外观模式的定义是:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
| 维度 | 描述 | 形象比喻 |
|---|---|---|
| 核心意图 | 隐藏系统的复杂性,提供一个简单的入口。 | 全屋智能面板。按一下“离家模式”,自动关灯、关空调、锁门、开监控。 |
| 主要优点 | 1. 简化调用:客户端无需了解底层的复杂逻辑; 2. 降低耦合:业务代码只依赖外观对象,不依赖底层子系统; 3. 灵活性:底层系统变动时,只需修改外观类。 | 酒店前台。你只需找前台(外观),前台会帮你联系厨师、保洁和司机。 |
| 主要缺点 | 如果设计不当,外观类可能变得过于庞大(演变成“上帝对象”)。 | 集权过度。如果前台辞职了,所有服务都找不到了。 |
2. 模式实现:从“琐碎”到“一键触发”
2.1 实战场景:跨浏览器兼容性封装
这是前端开发中最经典的“外观”。以前为了兼容 IE 和标准浏览器,我们必须写很多判断。
js
// 这是一个“外观”函数
function addEvent(el, type, fn) {
// 隐藏底层的判断逻辑
if (el.addEventListener) {
el.addEventListener(type, fn, false);
} else if (el.attachEvent) {
el.attachEvent('on' + type, fn);
} else {
el['on' + type] = fn;
}
}
// 业务代码:调用极其简单
addEvent(document.body, 'click', () => console.log('点击了'));2.2 实战场景:子系统整合 (家庭影院示例)
假设开启影院模式需要操作多个独立系统。
js
// 子系统 A:屏幕
class Screen {
up() { console.log('屏幕升起'); }
down() { console.log('屏幕降下'); }
}
// 子系统 B:音响
class Audio {
on() { console.log('音响开启'); }
setVolume(v) { console.log(`音量设为: ${v}`); }
}
// 外观类 (Facade)
class HomeTheaterFacade {
constructor() {
this.screen = new Screen();
this.audio = new Audio();
}
// 高层接口:一键观影
watchMovie() {
console.log('--- 准备观影 ---');
this.screen.down();
this.audio.on();
this.audio.setVolume(10);
}
}
// 客户端使用
const theater = new HomeTheaterFacade();
theater.watchMovie();3. 模式对比:相似模式的辨析
外观模式经常被拿来与适配器、中介者、命令模式对比:
| 模式 | 核心区别 | 侧重点 | 形象比喻 |
|---|---|---|---|
| 外观模式 | 封装并简化。接口是新的,逻辑是现有的。 | 易用性 | 全屋智能的“离家模式”按钮。 |
| 适配器模式 | 转换接口。目的是让原本不兼容的接口能一起工作。 | 兼容性 | 电源转接头(三孔转两孔)。 |
| 中介者模式 | 管理交互。解耦多个同事对象之间的多对多关系。 | 多边通信 | 机场塔台,负责调度所有飞机。 |
| 命令模式 | 封装请求。将一个请求封装为一个对象(包含执行者、方法、参数)。 | 解耦与操作控制 | 餐馆的点菜单。记录了谁点、吃什么、谁来做。 |
4. 常见问题 (FAQ)
4.1 什么时候该用外观模式?
- 答:
- 当你要为一个复杂的子系统提供一个简单入口时。
- 当客户端代码与多个子系统紧密耦合,导致难以维护时。
- 当你想要构建一个层次结构,让每一层都通过“外观”与外界通信时。
4.2 外观模式会限制我直接调用底层接口吗?
- 答:不会。 外观模式只是提供了一个“快捷方式”。如果某些高级用户需要更精细的控制,他们依然可以直接绕过外观类去调用底层的子系统。
4.3 外观模式和单例模式经常配合使用吗?
- 答:是的。 通常整个系统只需要一个外观实例(例如一个全局的 API 管理器),因此外观类往往被设计成单例。
4.4 它是“开闭原则”的拥护者吗?
- 答:不完全是。 增加新功能时,通常需要修改外观类。但在 JavaScript 中,外观模式更强调的是封装带来的代码整洁度和心智负担的降低。