09 外观(结构型)
一个子系统里一堆类、一长串调用顺序,客户端用得头大。给它配一个“一键搞定”的高层入口。讲清外观的化繁为简、它和适配器的区别,以及为什么 http.Get() 就是个外观。
作者 David更新于
09 外观 · Facade(结构型)
前三个结构型模式都在包“一个对象”。外观不一样——它包的是一整个子系统。
一、这是什么:家庭影院的“一键观影”
你家客厅堆着投影仪、功放、灯光、流媒体盒子。想看场电影,标准流程是:把灯调暗到 20%、开投影仪、切到流媒体输入源、开功放、音量调到 60、启动播放器选片……七八步,一步不能少,顺序还不能乱。
太累了。于是你装了个智能面板,上面就一个按钮:「观影模式」。一按,上面那七八步自动全做完。这个按钮就是外观:它背后是复杂的子系统,对你只暴露一个简单入口。
配套代码就是这个:HomeTheaterFacade.WatchMovie("盗梦空间") 一句话,内部替你协调投影仪、功放、灯光、播放器四个子系统,按正确顺序全部就位。
二、解决什么痛:客户端被子系统的细节淹没
一个功能强大的子系统,内部往往有很多类、很多步骤、很多“必须按这个顺序调”的隐含规则。如果让每个调用方都去直接对付这些细节:
- 调用方被迫理解一大堆内部知识——它明明只想“看个电影”,却要懂投影仪的输入源、功放的音量单位。
- 调用顺序的规则散落在各处,谁都可能调错(先开播放器后开投影仪,黑屏)。
- 子系统一改,所有调用方跟着遭殃——内部多加个“预热”步骤,几十处调用全要改。
外观隔离的“会变的东西”,是“子系统的内部结构与协作细节”。 它在客户端和子系统之间架一道“墙”,墙上开一扇简单的门(高层方法)。客户端只跟门打交道,子系统内部怎么折腾、加几个步骤、换个实现,都被挡在墙后,伤不到客户端。这直接呼应了迪米特法则(最少知识原则):别跟陌生人说话,只跟你的直接朋友(外观)说话。
三、怎么实现:一个高层方法,协调一群子系统
外观本身没什么玄机——它就是持有各子系统,在高层方法里按正确顺序把它们串起来。
// 外观:持有一群子系统
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) // ← 客户端这一切都不用知道
}客户端的世界一下子清净了:
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 服务器无数步骤——外观思想在框架层面的极致体现。
结构型过半。已出场的四位(适配器、装饰器、代理、外观)都在管“包装与简化”。接下来两位换个主题——管“多维度”和“树形结构”。下一篇桥接:当一个东西有两个独立变化的维度时,怎么不让类的数量乘起来爆炸。