# 09 外观（结构型）

> 一个子系统里一堆类、一长串调用顺序，客户端用得头大。给它配一个“一键搞定”的高层入口。讲清外观的化繁为简、它和适配器的区别，以及为什么 http.Get() 就是个外观。

- 作者：David（道雾轩）
- 专栏：经典设计模式浅析（https://daiw.org/manual/design-patterns.md）
- 最后更新：2026-07-12
- 原文：https://daiw.org/manual/design-patterns/facade
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 09 外观 · Facade（结构型）

前三个结构型模式都在包“一个对象”。外观不一样——**它包的是一整个子系统。**

## 一、这是什么：家庭影院的“一键观影”

你家客厅堆着投影仪、功放、灯光、流媒体盒子。想看场电影，标准流程是：把灯调暗到 20%、开投影仪、切到流媒体输入源、开功放、音量调到 60、启动播放器选片……七八步，一步不能少，顺序还不能乱。

太累了。于是你装了个智能面板，上面就一个按钮：**「观影模式」**。一按，上面那七八步自动全做完。这个按钮就是**外观**：它背后是复杂的子系统，对你只暴露一个简单入口。

配套代码就是这个：`HomeTheaterFacade.WatchMovie("盗梦空间")` 一句话，内部替你协调投影仪、功放、灯光、播放器四个子系统，按正确顺序全部就位。

## 二、解决什么痛：客户端被子系统的细节淹没

一个功能强大的子系统，内部往往有很多类、很多步骤、很多“必须按这个顺序调”的隐含规则。如果让每个调用方都去直接对付这些细节：

- **调用方被迫理解一大堆内部知识**——它明明只想“看个电影”，却要懂投影仪的输入源、功放的音量单位。
- **调用顺序的规则散落在各处**，谁都可能调错(先开播放器后开投影仪，黑屏)。
- **子系统一改，所有调用方跟着遭殃**——内部多加个“预热”步骤，几十处调用全要改。

<Callout type="info">
  **外观隔离的“会变的东西”，是“子系统的内部结构与协作细节”。** 它在客户端和子系统之间架一道“墙”，墙上开一扇简单的门(高层方法)。客户端只跟门打交道，子系统内部怎么折腾、加几个步骤、换个实现，都被挡在墙后，伤不到客户端。这直接呼应了**迪米特法则**(最少知识原则)：别跟陌生人说话，只跟你的直接朋友(外观)说话。
</Callout>

## 三、怎么实现：一个高层方法，协调一群子系统

外观本身没什么玄机——它就是持有各子系统，在高层方法里按正确顺序把它们串起来。

```go title="go/facade/main.go（核心）"
// 外观:持有一群子系统
type HomeTheaterFacade struct {
	projector *Projector
	amplifier *Amplifier
	lights    *Lights
	player    *StreamingPlayer
}

// 一个高层方法,把"观影"需要的所有子系统操作按序封装好
func (h *HomeTheaterFacade) WatchMovie(movie string) {
	h.lights.Dim(20)
	h.projector.On()
	h.projector.SetInput("流媒体")
	h.amplifier.On()
	h.amplifier.SetVolume(60)
	h.player.Play(movie)      // ← 客户端这一切都不用知道
}
```

客户端的世界一下子清净了：

```go
theater := NewHomeTheaterFacade()
theater.WatchMovie("盗梦空间")   // 一句话,七八步全搞定
theater.EndMovie()              // 一句话,全部关闭复位
```

## 四、有哪些坑

- **外观不是“墙”，是“门”——它不禁止你直接进子系统。** 这是外观和“封装/黑箱”的关键区别：提供了 `WatchMovie` 的便捷入口，**不代表**禁止高级用户绕过它、直接操作投影仪做精细控制。外观是“给大多数人的方便法门”，不是“唯一法门”。留这个口子很重要。
- **警惕外观膨胀成“上帝对象”。** 图省事，什么方法都往外观里塞，最后它变成一个几千行、无所不知、无所不管的巨型类——这比没有外观还糟。**一个外观对应一个清晰的使用场景/子系统**，别让它长成怪物。真需要多个场景，就拆多个外观。
- **和适配器分清：** 适配器是**转换一个**接口(让不兼容的能对接)，意图是“兼容”；外观是**简化一组**接口(给复杂子系统一个简单入口)，意图是“省事”。适配器改的是“接口长相”，外观改的是“使用难度”。

## 五、开源里怎么用

- **Go `http.Get(url)`——你天天用的外观。** 这一句背后，是 `http.Client`、`http.Transport`、`http.Request`、连接池、重定向处理一大套子系统。`http.Get` 把它们全封在身后，给你一个极简入口。同理 `os.ReadFile`、`os.WriteFile` 都是文件操作子系统的外观。
- **各类 SDK 的 `Client`**：云服务/支付/短信 SDK 里那个 `client.SendSMS(...)`，内部封装了鉴权签名、构造请求、重试、解析响应一整套流程。你调一个方法，它替你跑完全程——这是外观在商业 SDK 里最普遍的形态。
- **日志门面(SLF4J、Go `log/slog`)**：名字里就带“门面”(facade)。它给五花八门的日志后端提供统一的高层 API，你面向门面写日志，底层用 logback 还是 zap，门面替你隔离。
- **框架的“启动器”(Spring Boot Starter、各类 `bootstrap()`)**：一句 `SpringApplication.run()` 背后是扫描、装配、启动容器、起 Web 服务器无数步骤——外观思想在框架层面的极致体现。

---

结构型过半。已出场的四位(适配器、装饰器、代理、外观)都在管“包装与简化”。接下来两位换个主题——管“多维度”和“树形结构”。下一篇[桥接](https://daiw.org/manual/design-patterns/bridge)：当一个东西有**两个独立变化的维度**时，怎么不让类的数量乘起来爆炸。
