- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
本文基于 java-design-patterns 仓库中 facade 模块的官方文档(localization/es/facade/README.md)及其英文版(facade/README.md)撰写,结合模块源码与测试对模式实现进行纵深剖析。读完本文,你将掌握 Facade 模式的核心思想、在 Java 中的落地写法、适用与不适用的场景判断,以及本仓库"金矿开采"示例背后完整的调用链与验证方式。
一、模式定位:为复杂子系统提供统一接口
Facade(外观)是 GoF(Gang of Four)四组经典设计模式中的结构型模式。它的核心目的是:
为子系统中的一组接口提供一个统一的接口。外观定义了一个高层接口,让子系统更容易被使用。
换句话说,Facade 并不试图"消灭"子系统的复杂度,而是在子系统与客户端之间插入一个简单的门面对象,让客户端只需要面对这个门面,而不必逐个了解、编排子系统内部的各个类。
原文档用一个非常直观的真实场景来说明它:
"金矿是如何工作的?'嗯,矿工们下去挖出金子!'你这样说。这正是你相信的,因为你使用的是金矿在外部提供的简单接口,而内部它要做一大堆事情才能让这一切发生。这个面向复杂子系统的简单接口,就是 Facade。"
一句话概括:Facade 模式为复杂子系统提供了一个简化的接口。Wikipedia 的经典定义也印证了这一点——"外观是一个对象,它为更大的代码体(如一个类库)提供简化的接口"。
二、类图:先看结构,再看实现
模块自带的 PlantUML 源文件 facade/etc/facade.urm.puml 清晰地描述了本示例的类型关系,渲染后的类图如下:
Facade 模式类图:DwarvenGoldmineFacade 聚合 DwarvenMineWorker,三个具体工作者继承抽象基类
从类图(以及 facade/etc/facade-sequence-diagram.png 时序图)可以看到本模式的四类角色:
| 角色 | 本示例中的类 | 职责 |
|---|---|---|
| Facade(外观) | DwarvenGoldmineFacade | 持有子系统对象引用,对外提供startNewDay()、digOutGold()、endDay()等高层操作 |
| Subsystem(子系统) | DwarvenMineWorker及其三个子类 | 真正执行具体工作的类,客户端不直接操作它们 |
| Client(客户端) | App | 只依赖 Facade,不感知子系统内部细节 |
| 枚举协作 | Action(定义在DwarvenMineWorker内) | 以命令枚举的方式统一描述工人可执行的动作 |
三、编程示例:金矿里的矮人工人
接下来是原文档的完整编程示例。场景设定为一座金矿:矿上有三种矮人工人——挖隧道工(DwarvenTunnelDigger)、挖金工(DwarvenGoldDigger)和矿车操作员(DwarvenCartOperator)。客户(矿主)不直接指挥每个人,而是通过一个门面DwarvenGoldmineFacade来统一下令。
3.1 子系统基类:DwarvenMineWorker
首先是一个抽象基类DwarvenMineWorker(源码见 facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java):
@Slf4j public abstract class DwarvenMineWorker { public void goToSleep() { LOGGER.info("{} goes to sleep.", name()); } public void wakeUp() { LOGGER.info("{} wakes up.", name()); } public void goHome() { LOGGER.info("{} goes home.", name()); } public void goToMine() { LOGGER.info("{} goes to the mine.", name()); } private void action(Action action) { switch (action) { case GO_TO_SLEEP -> goToSleep(); case WAKE_UP -> wakeUp(); case GO_HOME -> goHome(); case GO_TO_MINE -> goToMine(); case WORK -> work(); default -> LOGGER.info("Undefined action"); } } public void action(Action... actions) { Arrays.stream(actions).forEach(this::action); } public abstract void work(); public abstract String name(); enum Action { GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK } }这个基类有两个值得注意的设计点:
Action枚举集中定义了所有可能的动作(睡觉、起床、回家、去矿井、工作),子类通过action(Action...)的变参版本一次接收多个动作,内部用Arrays.stream(actions).forEach(this::action)逐一执行——这正是 Facade 批量下令的底层支撑;- 基类把"唤醒、就寝、往返矿井"这些所有工人共有的日常动作固化下来,而把
work()("干什么活")和name()("我是谁")留给子类实现,体现了模板方法的思想与多态分发。
3.2 三个具体子系统类
然后是三个具体工人(源码分别见 DwarvenTunnelDigger.java、DwarvenGoldDigger.java、DwarvenCartOperator.java):
@Slf4j public class DwarvenTunnelDigger extends DwarvenMineWorker { @Override public void work() { LOGGER.info("{} creates another promising tunnel.", name()); } @Override public String name() { return "Dwarven tunnel digger"; } } @Slf4j public class DwarvenGoldDigger extends DwarvenMineWorker { @Override public void work() { LOGGER.info("{} digs for gold.", name()); } @Override public String name() { return "Dwarf gold digger"; } } @Slf4j public class DwarvenCartOperator extends DwarvenMineWorker { @Override public void work() { LOGGER.info("{} moves gold chunks out of the mine.", name()); } @Override public String name() { return "Dwarf cart operator"; } }三个子类各自只实现两件事:自己擅长的工作内容(work())和自己的名字(name())。它们彼此之间没有任何耦合,各自是独立的"子系统组件"。
3.3 门面类:DwarvenGoldmineFacade
为了统筹调度这些工人,示例提供了门面DwarvenGoldmineFacade(源码见 facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java):
public class DwarvenGoldmineFacade { private final List<DwarvenMineWorker> workers; public DwarvenGoldmineFacade() { workers = List.of( new DwarvenGoldDigger(), new DwarvenCartOperator(), new DwarvenTunnelDigger()); } public void startNewDay() { makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); } public void digOutGold() { makeActions(workers, DwarvenMineWorker.Action.WORK); } public void endDay() { makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); } private static void makeActions(Collection<DwarvenMineWorker> workers, DwarvenMineWorker.Action... actions) { workers.forEach(worker -> worker.action(actions)); } }这段代码浓缩了 Facade 模式的全部要点:
- 门面持有子系统实例:构造器中用
List.of(...)一次性组装出全部三名工人,并保存在private final List<DwarvenMineWorker> workers字段中——门面是子系统唯一已知的"联系人"; - 门面编排子系统流程:
startNewDay()(起床→去矿井)、digOutGold()(工作)、endDay()(回家→睡觉)都是由多个原子动作组合而成的高层业务操作; - 批量调度逻辑被收敛:私有方法
makeActions(workers, actions...)用workers.forEach(worker -> worker.action(actions))对每个工人下发同一批动作。注意这里action的变参版本是关键——一次调用即可让工人按顺序执行多个动作,而门面无需关心工人内部如何切换这些动作。
3.4 客户端如何使用门面
在 facade/src/main/java/com/iluwatar/facade/App.java 中,main方法完整演示了客户端只与门面打交道的过程:
public static void main(String[] args) { var facade = new DwarvenGoldmineFacade(); facade.startNewDay(); facade.digOutGold(); facade.endDay(); }客户端只看到三个方法调用,背后却是对三类工人、共 15 条动作指令的完整调度——这就是"统一接口简化复杂子系统"最直观的体现。
3.5 程序输出
运行上述代码,控制台输出如下(为便于阅读,省略了日志时间戳与类名前缀):
// Dwarf gold digger wakes up. // Dwarf gold digger goes to the mine. // Dwarf cart operator wakes up. // Dwarf cart operator goes to the mine. // Dwarven tunnel digger wakes up. // Dwarven tunnel digger goes to the mine. // Dwarf gold digger digs for gold. // Dwarf cart operator moves gold chunks out of the mine. // Dwarven tunnel digger creates another promising tunnel. // Dwarf gold digger goes home. // Dwarf gold digger goes to sleep. // Dwarf cart operator goes home. // Dwarf cart operator goes to sleep. // Dwarven tunnel digger goes home. // Dwarven tunnel digger goes to sleep.可以看到调度顺序完全符合门面编排的逻辑:清晨全员"起床→去矿井"(6 条日志)、白天全员"工作"(3 条日志)、收工全员"回家→睡觉"(6 条日志)。
四、调用链与测试验证:模式正确性的源码级证据
本仓库不仅给出了实现,还提供了完整的测试来证明门面的行为符合预期,值得逐一对照阅读:
- facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java:核心行为测试。它用一个
InMemoryAppender(继承 Logback 的AppenderBase<ILoggingEvent>)在内存中捕获全部日志,然后依次断言:startNewDay()后恰好产生 6 条日志,且内容全部为"起床/去矿井"(assertEquals(6, appender.getLogSize()));digOutGold()后累计 9 条日志,新增的 3 条分别是三人的"工作"内容;endDay()后累计 15 条日志,新增的 6 条分别是三人的"回家/睡觉"。- 这个"每阶段精确断言日志条数"的写法,实际上验证了门面对子系统调用的精确编排——不多调、不少调。
- facade/src/test/java/com/iluwatar/facade/AppTest.java:入口冒烟测试,
assertDoesNotThrow(() -> App.main(new String[]{}))保证示例主程序可直接运行而不抛异常。
完整的调用链可归纳为:
App.main() └─ new DwarvenGoldmineFacade() // 组装 3 名工人(List.of) └─ startNewDay() → makeActions(workers, WAKE_UP, GO_TO_MINE) │ └─ worker.action(WAKE_UP, GO_TO_MINE) │ └─ switch → wakeUp() / goToMine() // 每个工人 └─ digOutGold() → makeActions(workers, WORK) │ └─ worker.action(WORK) → work() // 多态分发 └─ endDay() → makeActions(workers, GO_HOME, GO_TO_SLEEP) └─ worker.action(GO_HOME, GO_TO_SLEEP)运行验证也很简单:在仓库根目录使用./mvnw -pl facade test(Windows 下为mvnw.cmd -pl facade test)即可执行该模块的全部单元测试。仓库提供的 Maven 包装器脚本位于 mvnw / mvnw.cmd。
五、适用场景:什么时候该用 Facade
原文档给出了三个典型的适用场景,结合本示例可以这样理解:
- 你想为复杂子系统提供简单接口。子系统会随着演化变得越来越复杂——大多数模式应用后都会产生更多、更小的类,这让子系统更可复用、更易定制,但同时也让不需要定制它的客户端更难使用。此时 Facade 能提供一个对大多数客户端"够用"的默认视图;只有确实需要深度定制的客户端才需要绕过门面去看子系统内部。
- 客户端与抽象的实现类之间存在大量依赖。引入 Facade 可以把子系统与客户端、其他子系统解耦,从而提升子系统的独立性与可移植性。在本示例中,
App完全不引用任何工人实现类,只依赖DwarvenGoldmineFacade一个类。 - 你想对子系统进行分层。用 Facade 为每个子系统层级定义一个入口点;如果子系统之间相互依赖,可以让它们只通过各自的 Facade 通信,从而简化依赖关系。
从反面看,英文版 README(facade/README.md)还提醒了一个需要警惕的代价:如果门面实现不当,它可能退化为一个"上帝对象"(god object),与应用中的全部类耦合在一起。因此门面的职责应当克制——只做"接口简化与流程编排",而不是把所有业务逻辑都塞进门面。
六、与其他结构型模式的关系
- Adapter(适配器):Facade 提供的是统一接口(把一堆接口收敛成一个),而 Adapter 解决的是让两个已有接口协同工作。前者是"重新设计一个入口",后者是"改造其中一个接口去适配另一个"。
- Mediator(中介者):Facade 定义的是更简单的接口,方向是"客户端→子系统"的单向收敛;而 Mediator 在对象之间集中化复杂通信与控制,协调的是平级对象之间的多向交互。
仓库中这两个模式均有完整实现与文档,可对照学习:adapter/README.md、mediator/README.md。
七、总结
Facade 模式是"为复杂度建立边界"的经典手段。通过本仓库的金矿示例可以看到它的完整形态:子系统类(三个矮人工人)各司其职、互不感知;门面类(DwarvenGoldmineFacade)在构造时组装子系统、在方法中编排动作序列、用变参action机制批量下发指令;客户端(App)只面对三个高层方法。配合精确断言日志数量的单元测试,整个模式的正确性、调用链与边界都得到了源码级验证。
在实际工程中,当你在面对"一堆类组合起来才能完成一个操作"的场面时,不妨为这个组合引入一个 Facade——它会成为你系统中一处值得长期维护的"稳定接口层"。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Actual-Server外观模式:复杂子系统简化接口
Actual Server外观模式:复杂子系统简化接口 痛点:银行API集成的复杂性挑战 在现代个人财务管理工具开发中,银行API集成是一个极具挑战性的任务。每
后端金融科技数据同步Swift外观模式:简化复杂子系统接口的利器
Swift外观模式:简化复杂子系统接口的利器 外观模式是Swift设计模式中的重要结构型模式,它为复杂子系统提供了一个统一的简化接口。这种模式让开发者能够通过单
示例工程如何利用Composio外观模式简化复杂系统接口:完整指南
如何利用Composio外观模式简化复杂系统接口:完整指南 Composio是一个为智能代理提供精心设计工具的开源项目,通过外观模式(Facade Patter
人工智能AI Agent工具调用MCP 服务MCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考