10. 外观模式(Facade)

问题:子系统接口复杂,调用者不想了解内部细节。

核心:提供一个高层简化接口,内部协调子系统。

class Projector {
  on(): void { console.log('投影仪开机'); }
  setInput(): void { console.log('切换到 HDMI'); }
}

class SoundSystem {
  on(): void { console.log('音响开机'); }
  setVolume(v: number): void { console.log(`音量设为 ${v}`); }
}

class Lights {
  dim(): void { console.log('灯光调暗'); }
}

// 外观
class HomeTheaterFacade {
  #projector = new Projector();
  #sound = new SoundSystem();
  #lights = new Lights();

  watchMovie(): void {
    this.#lights.dim();
    this.#projector.on();
    this.#projector.setInput();
    this.#sound.on();
    this.#sound.setVolume(50);
  }
}

const theater = new HomeTheaterFacade();
theater.watchMovie();
package facade

import "fmt"

type Projector struct{}
func (p Projector) On()       { fmt.Println("投影仪开机") }
func (p Projector) SetInput() { fmt.Println("切换到 HDMI") }

type SoundSystem struct{}
func (s SoundSystem) On()        { fmt.Println("音响开机") }
func (s SoundSystem) SetVolume(v int) { fmt.Printf("音量设为 %d\n", v) }

type Lights struct{}
func (l Lights) Dim() { fmt.Println("灯光调暗") }

type HomeTheaterFacade struct {
  projector Projector
  sound     SoundSystem
  lights    Lights
}

func NewHomeTheaterFacade() *HomeTheaterFacade {
  return &HomeTheaterFacade{}
}

func (f *HomeTheaterFacade) WatchMovie() {
  f.lights.Dim()
  f.projector.On()
  f.projector.SetInput()
  f.sound.On()
  f.sound.SetVolume(50)
}

实战场景

  • 结账流程:下单、锁库存、计算优惠、创建支付单、写订单日志、发通知都藏在 checkout() 后面。
  • 文件上传:调用者只调用 upload(),内部完成压缩、鉴权、分片、重试、写元数据和返回 URL。
  • 用户注册:注册接口内部串起参数校验、密码加密、创建用户、发送邮件、初始化默认设置。
  • 前端 API Client:页面只调用 api.posts.create(),不用关心 fetch、headers、错误转换、刷新 token。
  • 复杂 SDK 包装:第三方 SDK 初始化参数多、调用链长时,项目内部提供一个更小的门面方法。
  • 报表生成:外部只传报表类型和时间范围,内部负责拉数据、聚合、渲染模板、导出文件。
  • 音视频播放控制:播放、暂停、音量、字幕、清晰度、缓冲状态背后有多个子系统,UI 只接触播放器门面。
  • 部署脚本:一个 deploy() 封装构建、上传、迁移、清缓存、健康检查和回滚入口。
  • 权限判断:业务代码调用 can(user, action, resource),内部再组合角色、策略、资源归属、租户边界。
  • 微前端宿主:应用加载、沙箱、样式隔离、路由同步、通信机制可以由统一门面隐藏。