周一早上,小王是被饿醒的,醒来就想喝胡辣汤。
前几天他刚在网上囤了几样老家的吃食:胡辣汤料、豆腐菜、饸饹面,都是下锅煮一煮就能吃的。味道跟老家店里比还差着一截,但整体是那个意思——对出门在外的人来说,这一口已经很知足了。
公司是弹性打卡,最晚九点半到,算上电车、地铁和步行,他最晚八点十分就得出家门。
真要从零做一碗胡辣汤,胡椒、花椒、八角十几种香料得按比例配,高汤得提前吊,面筋得洗,芡得勾得刚刚好——这一套下来,班也不用上了。
他撕开了一包胡辣汤料。水开下料,粉条、木耳、豆腐皮下锅,咕嘟几分钟,勾个薄芡,出锅点香油。配着水煎包,一碗下肚,出门上班。

一包料下锅,十几种香料的事,就不用他操心了
这包料做的事,值得琢磨:香料还是那十几种,工序一道也没少,但小王只跟"料包"这一个东西打交道。配比、顺序、火候的讲究,都收在了包装里面。
今天聊的就是这个思路:外观模式(Facade),也叫门面模式——给一组复杂的子系统,留一个简单统一的入口。系列第九篇。
01 从零做一碗,得先认识多少东西
先看看没有料包的日子。
想喝胡辣汤,你得认识香料架,知道胡椒是灵魂、剩下的各放多少;得认识汤锅,知道什么时候下料、什么时候下配菜;还得认识芡汁,知道勾早了容易泄、勾晚了汤不挂勺。
用代码写出来,大概是这个样子:
// 三个子系统,各自管一摊
class SpiceRack {
void mix() { System.out.println("香料架:胡椒为主,十几种香料按比例配好"); }
}
class StockPot {
void boil() { System.out.println("汤锅:高汤烧开"); }
void simmer() { System.out.println("汤锅:下配菜,小火咕嘟几分钟"); }
}
class StarchSlurry {
void thicken() { System.out.println("芡汁:勾薄芡,汤变得浓稠挂勺"); }
}
香料、汤锅、芡汁,各管一摊
做汤的人得把这一整套都学会,按对的顺序挨个调用:
SpiceRack spices = new SpiceRack();
StockPot pot = new StockPot();
StarchSlurry slurry = new StarchSlurry();
pot.boil(); // 先烧汤
spices.mix(); // 再下配好的香料
pot.simmer(); // 下配菜,小火煮
slurry.thicken(); // 最后勾芡,顺序不能乱
每一步都得自己记,顺序错了味道就不对
问题不在某一行代码,在于"先烧汤、再配香料、后勾芡"这份知识,装在每个做汤的人脑子里。家里谁想喝,谁就得从头学一遍;学漏一步,一锅汤就翻车。
02 料包只留一个动作
料包的思路是:把这份知识从人脑子里挪出来,收进一个固定的地方。
class HuLaTangFacade {
private final SpiceRack spices = new SpiceRack();
private final StockPot pot = new StockPot();
private final StarchSlurry slurry = new StarchSlurry();
void cook() { // 对外只留一个动作
pot.boil();
spices.mix();
pot.simmer();
slurry.thicken();
System.out.println("出锅,点香油");
}
}
配比和顺序,都收进 cook() 里
小王这边,只剩一句:
new HuLaTangFacade().cook(); // 他只需要知道这一句
不用认识十几种香料,也能喝上胡辣汤
注意一件事:SpiceRack、StockPot、StarchSlurry 一个都没少,各自的方法也没改。外观模式不是把子系统砍掉,而是在它们前面加了一层,把"谁按什么顺序调用谁"收拢起来。

子系统没变,变的是做汤的人要懂多少
外观模式的价值,不是让系统变简单,而是让"用系统"这件事变简单。
03 复杂没有消失,只是换了个地方
这里有两处得挑明,别让类比骗了。
第一处:真实的料包是个黑盒。比例在工厂里就定死了,你想少放点胡椒,只能自己往碗里想办法。代码里的门面不是黑盒——cook() 的源码就摆在那儿,你随时可以打开看,也可以绕过它:
HuLaTangFacade soup = new HuLaTangFacade();
soup.cook(); // 省事的走门面
new SpiceRack().mix(); // 内行也可以直接动用子系统
门面是默认的捷径,不是唯一的入口
这正是外观模式和"封装成黑盒"的区别:子系统不知道门面的存在,门面也不该拦着你直接用它。想自己多配一把香料的人,路得给他留着。
第二处:料包并没有把复杂消灭,只是把复杂的活提前在工厂里干完了。代码里也一样——顺序和配比只是从每个调用方脑子里,挪进了 cook() 一个方法里。真出了 bug,比如汤不够味,你还是得钻进 cook() 里面,一步一步查是哪个子系统出了问题。
门面对外挡的是"使用成本",挡不住"排查成本"。 以为有了门面就不用懂子系统,迟早要在一锅不对味的汤面前补课。
04 门面、转接头和中间人
前几篇聊过的模式,跟门面看着有点像,得把边界划清。
代理(Day 7)是"代表某一个人":打车平台挡在你和司机中间,管你能不能碰到真司机,重点是控制访问。门面是"收拢一堆零件":香料架、汤锅、芡汁谁也不被代表,它们照常干活,只是你不用再挨个找它们。
适配器是"换个接口的形状":出国带转换插头,是因为插孔和插头对不上,得转成能插进去的形状。门面不管形状对不对,管的是"接口太多、太碎",把它们合并成少数几个。一句话区分:适配器改接口,代理控访问,外观简接口。
Java 生态里最典型的门面,你可能天天在用:SLF4J,名字就叫 Simple Logging Facade for Java。你代码里只写 Logger 和 log.info(...),底下是 Logback 还是 Log4j2、怎么滚动文件、怎么异步刷盘,都不用你挨个认识。Spring 的 JdbcTemplate 也是一路:获取连接、执行 SQL、处理结果集、关闭资源,这一长串原本要自己按顺序伺候的步骤,收进了 query()、update() 几个方法里。
AI 可以一秒生成一个门面类,但"哪些调用该收进这个入口、哪些该留在外面",它替不了你做主——这取决于你希望别人怎么用这套系统。
05 什么时候,别急着包门面
不是所有东西都值得一个料包。
只调用一个简单对象时,别包。 烧壶白开水,直接按开关就行,犯不上先设计一个"饮水门面",把 press() 改名成 drink()。
别让门面长成上帝类。 门面的本职是编排调用顺序,不是把业务规则也长进去。今天往里塞个"自动配早餐",明天塞个"顺便叫车",最后门面本身成了全家最复杂的地方——那等于把十几种香料又倒回了桌上。
别把门面变成强制关卡。 前面说过,内行要能绕过它。一旦强制所有人只能走门面,想定制的人没法定制,门面就从"捷径"变成了"路障"。
判断标准可以很简单:调用方是不是总在重复同一串样板调用? 是,就值得收进一个入口;不是,直接调用反而更明白。
06 喝完汤,出门上班
小王那碗胡辣汤,从撕料到喝完没花多少工夫,出门、上班,一点没耽误。
香料还是那十几种,工序一道没少,只是这个早晨,它们不必都经过他。
跟前面八篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把要做的事做成对象,代理管谁能碰到真东西,观察者管状态变了告诉谁,外观管一堆零件只留一个入口。
带走一句话:外观 = 给一堆子系统,留一个简单统一的入口。
复杂没有消失,只是不必再经过你。
愿你周一的这碗汤,也有人替你配好了料。愿出门在外的人,都能用一包料的工夫,煮出一口老家的味道。
参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF 原著)——外观模式的原始定义:为子系统中的一组接口提供一个一致的界面,定义一个高层接口,使子系统更容易使用
- 《Head First 设计模式》第 7 章——适配器与外观,以及"最少知识原则":别跟太多对象交朋友
- SLF4J 官方手册——Simple Logging Facade for Java:日志门面如何统一入口、切换底层实现
- Spring 官方文档 · JdbcTemplate——把 JDBC 的样板步骤收进少量方法的经典实践