Skip to content

外观模式

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 什么时候该用外观模式?

    1. 当你要为一个复杂的子系统提供一个简单入口时。
    2. 当客户端代码与多个子系统紧密耦合,导致难以维护时。
    3. 当你想要构建一个层次结构,让每一层都通过“外观”与外界通信时。

4.2 外观模式会限制我直接调用底层接口吗?

  • 答:不会。 外观模式只是提供了一个“快捷方式”。如果某些高级用户需要更精细的控制,他们依然可以直接绕过外观类去调用底层的子系统。

4.3 外观模式和单例模式经常配合使用吗?

  • 答:是的。 通常整个系统只需要一个外观实例(例如一个全局的 API 管理器),因此外观类往往被设计成单例。

4.4 它是“开闭原则”的拥护者吗?

  • 答:不完全是。 增加新功能时,通常需要修改外观类。但在 JavaScript 中,外观模式更强调的是封装带来的代码整洁度心智负担的降低