【从0开始学设计模式-11| 外观模式】

作者:我爱cope日期:2026/4/24

在这里插入图片描述

概念

外观模式(Facade Pattern) 是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。

定义

为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。

[!tip]

我们可以通过这样一个生活的例子理解外观模式:

  • 我们在生活中使用的智能洗衣机,外观模式就是提供了多个功能,用户只需选择一个模式,按下启动键,洗衣机内部就会自动协调所有子系统,按预设程序完成

外观模式的结构

在这里插入图片描述

外观模式包含如下两个角色:

  • 外观(Facade)提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求,知晓如何操作一切活动部件。
  • 附加外观 (Additional Facade):可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。
  • 复杂子系统 (Complex Subsystem):由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。
  • 客户端 (Client):使用外观代替对子系统对象的直接调用。

外观模式的实现

类图设计

下面是一个矮人挖金矿的过程,使用Facade模式来演示其流程

在这里插入图片描述

代码实现

  • 复杂子系统
1public abstract class DwarfMineWorker {  
2  public abstract String name();  
3  public abstract void work();  
4  public void goToSleep() {  
5    System.out.println(name() + ":哈哈哈,彩票中了1个亿,明天当场辞职。");  
6  }  
7  public void wakeUp() {  
8    System.out.println(name() + ":万恶的东东哥,就知道催,都还没有睡醒。");  
9  }  
10  public void goHome() {  
11    System.out.println(name() + ":终于可以回家了,结束了被剥削的一天。");  
12  }  
13  public void goToMine() {  
14    System.out.println(name() + ":别催了,我到矿场了。");  
15  }  
16  private void action(Action action) {  
17    switch (action) {  
18      case WAKE_UP:  
19        wakeUp();  
20        break;  
21      case GO_TO_MINE:  
22        goToMine();  
23        break;  
24      case WORK:  
25        work();  
26        break;  
27      case GO_HOME:  
28        goHome();  
29        break;  
30      case GO_TO_SLEEP:  
31        goToSleep();  
32        break;  
33      default:  
34        System.out.println(name() + "不加钱,休想。");  
35        break;  
36    }  
37  }  
38  public void action(Action... actions) {  
39    Arrays.stream(actions).forEach(this::action);  
40  }  
41  enum Action {  
42    GO_TO_SLEEP,  
43    WAKE_UP,  
44    GO_HOME,  
45    GO_TO_MINE,  
46    WORK;  
47  }  
48}  
1public class DwarfCartOperator extends DwarfMineWorker {  
2  @Override  
3  public String name() {  
4    return "矿车手——小图";  
5  }  
6  @Override  
7  public void work() {  
8    System.out.println(name() + ":这一车运完休息一下吧!");  
9  }  
10}  
1public class DwarfGoldDigger extends DwarfMineWorker {  
2  @Override  
3  public String name() {  
4    return "挖矿者——小超";  
5  }  
6  @Override  
7  public void work() {  
8    System.out.println(name() + ":挖呀挖呀,快10T了吧!");  
9  }  
10}  
1public class DwarfTunnelDigger extends DwarfMineWorker {  
2  @Override  
3  public String name() {  
4    return "矿道工——小海";  
5  }  
6  @Override  
7  public void work() {  
8    System.out.println(name() + ":先把矿道维修好了再去修新的矿道吧!");  
9  }  
10}  
  • 外观
1import java.util.Arrays;  
2import java.util.List;  
3public class DwarfGoldMineFacade {  
4  private final List<DwarfMineWorker> workers;  
5  public DwarfGoldMineFacade() {  
6    workers = Arrays.asList(  
7      new DwarfTunnelDigger(), // 小海  
8      new DwarfGoldDigger(),   // 小超  
9      new DwarfCartOperator()  // 小图  
10    );  
11  }  
12  public void startNewDay() {  
13    workers.forEach((worker) ->  
14                    worker.action(DwarfMineWorker.Action.WAKE_UP, DwarfMineWorker.Action.GO_TO_MINE)  
15                   );  
16  }  
17  public void digOutGold() {  
18    workers.forEach((worker) ->  
19                    worker.action(DwarfMineWorker.Action.WORK)  
20                   );  
21  }  
22  public void endDay() {  
23    workers.forEach((worker) ->  
24                    worker.action(DwarfMineWorker.Action.GO_HOME, DwarfMineWorker.Action.GO_TO_SLEEP)  
25                   );  
26  }  
27}  
  • 客户端
1public class Client {  
2  public static void main(String[] args) {  
3    DwarfGoldMineFacade facade = new DwarfGoldMineFacade();  
4    // 通过外观类一键开启矿工的一天  
5    facade.startNewDay();  
6    System.out.println("--- 休息时间结束,开始干活 ---");  
7    facade.digOutGold();  
8    System.out.println("--- 下班时间到 ---");  
9    facade.endDay();  
10  }  
11}  

在这里插入图片描述

TIP: 在标准的外观模式结构图中,如果需要增加、删除或更换与外观类交互的子系统类,必须修改外观类或客户端的源代码,这将违背开闭原则,因此可以通过引入**抽象外观类**来对系统进行改进,在一定程度上可以解决该问题。

例如:子系统使用的是阿里云服务,但是第二版需要变成腾讯云,那我们可以怎么改?

  • 使用抽象外观,一个子类实现阿里云,一个子类实现腾讯云

外观模式适用环境

在项目开发中, 如果我们需要一个其他人开发的类, 但是还没有开发好, 那么我们也可以使用外观类来写点测试数据先运行先, 等到其他人开发好后, 直接在外观类进行替换, 这样就不需要修改我们已经写好的代码了.

举个例子:

假设你此时需要对接同时写的一个支付接口,但是同时还没写完

  • 定义外观类
1public class PaymentFacade {  
2 public boolean pay(double amount) {  
3   // 先在这里写死,模拟支付永远成功  
4   System.out.println("【临时调试】正在使用外观类模拟支付:" + amount + "元");  
5   return true;  
6 }  
7}  
  • 业务代码
1public class OrderService {  
2 //引入外观类  
3 private PaymentFacade paymentFacade = new PaymentFacade();  
4 public void completeOrder() {  
5   //这里假设逻辑已经写好了,直接调用外观类  
6   if (paymentFacade.pay(100.0)) {  
7     System.out.println("订单处理成功!");  
8   }  
9 }  
10}  
  • 同事写好之后,改一下外观类
1public class PaymentFacade {  
2 // 引入同事写好的支付接口  
3 private RealBankService realService = new RealBankService();  
4 public boolean pay(double amount) {  
5   // 将请求转发给真实的子系统  
6   // 哪怕同事的方法名和你起的不一样,也可以在这里转化掉  
7   return realService.executeTransaction(amount, "CNY");  
8 }  
9}  

业务代码保持不变,无需改动

主要优点

  • 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  • 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  • 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

主要缺点

  • 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。(外观类只用两个,子系统有10个)
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。(解决:抽象外观类)

适用环境

在以下情况下可以考虑使用外观模式:

  • 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
  • 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

如果这篇文章对你有帮助,欢迎点赞、评论、关注、收藏。你们的支持是我前进的动力!


【从0开始学设计模式-11| 外观模式】》 是转载文章,点击查看原文


相关推荐


VPS 买回来第一天该干什么?我的开机必做清单
小墨同学boy2026/4/16

很多人买来第一台VPS可能不知道干什么,是到手之后直接装宝塔,然后部署一个WordPress 博客丢上去跑了两天。可能过几天你就会发现 auth.log 里全是暴力破解记录,SSH 端口没有改还是默认的 22,root 密码没有做修改还是简单的 8 数字。最离谱的是你没有一开始测试号,最后发现那台机器的网络在晚高峰根本不能用,到最后可以部署完了才发现的,前面装的东西全白费功夫。 所以我才要写这篇文章,介绍我的流程:新机器到手,不要一上来就按照服务,先按照我的固定流程把去设置一遍服务器。整个过程其


# KubeBlocks for MSSQL 高可用实现
小猿姐2026/4/8

背景 Microsoft SQL Server(MSSQL)是由微软开发的一款关系型数据库管理系统。最初仅支持在 Windows 平台上运行,自 2017 版本起开始支持 Linux 系统,这一变化为 MSSQL 的容器化部署提供了可能。 MSSQL 提供了名为 Availability Group(可用性组,下文简称AG) 的多数据库复制管理特性,该特性支持在多个节点上实现数据库的多副本冗余,从而提升数据可靠性和服务连续性。在 Windows 平台上,MSSQL 通过与 Windows Ser


AI时代,我们的任务不应沉溺于与 AI 聊天 - 🤔 从“对话式编程”迈向“数字软件工厂”
阿文WUTAI感话2026/3/31

2026年,AI辅助开发的正确打开方式 在 2026 年的今天,研发工程师已经意识到,AI 辅助开发不应只是零散的提示词,而是一套 有标准、有性能、有角色、有流程 的系统工程 。通过 OpenSpec、Everything Claude Code (ECC)、gstack 以及新增的 superpowers 的深度协同,我们可以构建起一套现代化的"数字软件工厂",让研发协作工作流与状态流转更加符合项目的确定性需求 一、核心概念:先对齐术语,避免鸡同鸭讲 要驾驭这套工厂,首先需要对齐底层的专业概


家政服务小程序预约上门服务维修保洁上门服务在线派单技师入驻-ym7K
2601_952013762026/3/22

一、后台管理端核心功能 1. 系统基础配置 提供基础设置、店铺管理、家政管理三大核心配置入口,支撑系统整体运行。支持家政分类管理,可灵活划分保洁、月嫂、家电维修等服务类型。 2. 家政服务发布与管理 发布家政服务: 选择对应家政分类,填写服务标题。配置所在区域、家政公司名称、联系人及联系电话。设定家政性质:免费预约、预约金、实价三种模式可选。通过富文本编辑器编辑服务详情,支持图文排版。 管理家政:对已发布服务进行编辑、上下架等操作。管理订单:查看、处理用户预约订单,跟踪服务进度。服


Codex 工程化实践指南:深入理解 AGENTS.md、SKILL.md 与 MCP
Lei_official2026/3/14

AI 就像自动驾驶,其价值并非让没摸过方向盘的新手上路开车,而在于为熟练的驾驶者节约精力和时间。 在 Codex 的设计中,有三个非常关键的概念: AGENTS.md SKILL.md MCP(Model Context Protocol) 如果把 Codex 看成一个 “AI 工程师”,那么这三个概念相当于: 概念角色AGENTS.md团队开发规范SKILL.md可复用工作流MCP外部系统接口 注意这里的“团队开发规范”不是指人类工程师所组成


端侧RAG实战指南
稀有猿诉2026/3/6

本文译自「On-Device RAG for App Developers: Embeddings, Vector Search, and Beyond」,原文链接medium.com/google-deve…,由Sasha Denisov发布于2026年2月21日。 我们已经探讨了离线 AI 代理的重要性 和如何通过函数调用赋予它们工具。现在,让我们通过赋予它们记忆——即使用 RAG(检索增强生成)搜索和检索你的私有数据——来完善整个图景。 当我开始构建 Flutter Gemma 时,


Gemini 3.1 Pro 正式发布:一次低调更新,还是谷歌的关键反击?
IvanCodes2026/2/25

今天凌晨,谷歌发布了新一代模型——Gemini 3.1 Pro 没有大型发布会,没有提前预热,甚至连宣传节奏都显得克制。 很多人会把它看作 Gemini 3 的小版本升级,但从目前披露的测试数据和演示能力来看,这更像是一次结构性强化,而不是简单的参数迭代。 如果说 Gemini 3 是谷歌重新回到核心竞争区间的标志,那么 Gemini 3.1 Pro,则明显带着更强的实战优化意味。 它在几个关键方向上给出了非常明确的信号:谷歌不只是追赶者。 性能升级:从可用到强势竞争 这次升


React 性能优化:图片懒加载
NEXT062026/2/17

引言 在现代 Web 应用开发中,首屏加载速度(FCP)和最大内容绘制(LCP)是衡量用户体验的核心指标。随着富媒体内容的普及,图片资源往往占据了页面带宽的大部分。如果一次性加载页面上的所有图片,不仅会阻塞关键渲染路径,导致页面长时间处于“白屏”或不可交互状态,还会浪费用户的流量带宽。 图片懒加载(Lazy Loading)作为一种经典的性能优化策略,其核心思想是“按需加载”:即只有当图片出现在浏览器可视区域(Viewport)或即将进入可视区域时,才触发网络请求进行加载。这一策略能显著减少首屏


【Linux】进程信号(上半)
Lsir10110_2026/2/8

当我们想要强行终止掉前台进程的时候,只需要按下Ctrl+c即可,但是Ctrl+c是如何精准杀掉前台进程的? 一、信号概念 1.如何理解信号 假设点了一份外卖,外卖员到了楼下会给你发信息或者打电话,那么这通电话或者这个信息就是信号,也就是用来提醒你需要去做某事的一种通知手段。那么对照Linux系统,信号就是内核向进程发送的通知,提醒进程需要去完成某个任务。 2.信号的特点 对照外卖例子,当点完外卖,我们肯定不会一直在门口等着外卖员,而是先忙手中的事情,比如打游戏,那么这个过程就叫做异步


JSyncQueue——一个开箱即用的鸿蒙异步任务同步队列
江澎涌2026/1/31

零、JSyncQueue JSyncQueue 是一个开箱即用的鸿蒙异步任务同步队列。 项目地址:github.com/zincPower/J… 一、JSyncQueue 有什么作用 在鸿蒙应用开发中,有时需要让多个异步任务按顺序执行,例如状态的转换处理,如果不加控制,会因为执行顺序混乱而产生一些莫名其妙的问题。 所以 JSyncQueue 提供了一个简洁的解决方案: 保证顺序执行:所有任务严格按照入队顺序执行,即使任务内部有异步操作也能保证顺序 两种执行模式:支持 "立即执行" 和 "延时执

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客