设计模式——外观设计模式(结构型)

article/2025/8/7 7:46:31

摘要

本文介绍了外观设计模式,它是一种结构型设计模式,通过引入一个外观类来封装复杂子系统的调用细节,对外提供简单统一的接口。文中通过生活类比、关键角色介绍、使用场景分析以及结构说明等方面对这一模式进行了全面阐述,还涉及了实现方式、适合场景、实战示例和相关思考,有助于读者深入理解外观设计模式的原理和应用。

1. 外观设计模式定义

为复杂子系统提供一个统一的高层接口,使得子系统更易使用。外观模式通过引入一个“外观类(Facade)”,封装内部子系统的调用细节,对外暴露一个简单、统一的接口,隐藏系统的复杂性

1.1. 📦 举个通俗例子(生活类比):

点外卖 = 外观模式:

你使用美团 App 点外卖,只用点菜、下单,不需要关心

  • 餐厅是否接单(子系统 A)
  • 骑手怎么接单(子系统 B)
  • 结算怎么走账(子系统 C)

这就是“外观类”统一封装的行为。

1.2. ✅ 关键角色:

角色

说明

Facade(外观)

高层接口,封装子系统的复杂逻辑,对外提供简洁接口。

SubSystem(子系统)

一组类或模块,完成具体业务逻辑。外观类内部会协调调用它们。

Client(客户端)

只依赖外观类,屏蔽了对内部子系统的直接访问

1.3. ✅ 使用场景:

  • 系统结构复杂,希望对外提供简化接口;
  • 多个系统或模块集成,希望统一接入方式;
  • 用于分层架构(如 Controller -> Service -> Facade -> Subsystem);
  • 旧系统封装重构:用外观封装老接口,屏蔽调用细节。

2. 外观设计模式结构

外观模式包含如下角色:

  • Facade: 外观角色
  • SubSystem:子系统角色

2.1. 外观设计模式类图

2.2. 外观设计模式时序图

3. 外观设计模式实现方式

外观设计模式(Facade Pattern)的实现方式非常清晰明确:通过封装多个子系统的复杂调用逻辑,统一对外提供一个简单接口。

3.1. ✅ 实现步骤(标准实现方式)

3.1.1. 🔹 步骤 1:定义多个子系统(SubSystem)类

@Service
public class RiskScoreService {public int getRiskScore(String userId) {System.out.println("计算用户风险分...");return 75;}
}@Service
public class BlacklistService {public boolean isBlacklisted(String userId) {System.out.println("检查黑名单...");return false;}
}@Service
public class CreditService {public int getCreditLimit(String userId) {System.out.println("获取信用额度...");return 5000;}
}

3.1.2. 🔹 步骤 2:定义外观类(Facade)

@Servcie
public class RiskFacade {@Autowiredprivate final RiskScoreService riskScoreService;@Autowiredprivate final BlacklistService blacklistService;@Autowiredprivate final CreditService creditService;public void assessUserRisk(String userId) {System.out.println("开始用户风控评估...");if (blacklistService.isBlacklisted(userId)) {System.out.println("用户被拉黑,拒绝服务!");return;}int score = riskScoreService.getRiskScore(userId);int credit = creditService.getCreditLimit(userId);System.out.println("风控评估完成,风险分:" + score + ",信用额度:" + credit);}
}

3.1.3. 🔹 步骤 3:客户端只使用 Facade,不关心子系统

public class Client {public static void main(String[] args) {RiskFacade facade = new RiskFacade();facade.assessUserRisk("user123");}
}

3.2. ✅ 在 Spring 项目中的实现方式

如果项目使用 Spring 框架,我们通常会将子系统类标记为 @Service,将外观类作为一个统一入口暴露:

3.2.1. 🔹 子系统类(Spring Bean)

@Service
public class RiskScoreService { ... }@Service
public class BlacklistService { ... }@Service
public class CreditService { ... }

3.2.2. 🔹 外观类作为统一接口

@Servcie
public class RiskFacade {@Autowiredprivate RiskScoreService riskScoreService;@Autowiredprivate BlacklistService blacklistService;@Autowiredprivate CreditService creditService;public void assess(String userId) {// 同上,统一调用子服务}
}

3.2.3. 🔹 Controller 层只依赖外观类

@RestController
@RequestMapping("/risk")
public class RiskController {@Autowiredprivate RiskFacade riskFacade;@GetMapping("/assess")public String assess(@RequestParam String userId) {riskFacade.assess(userId);return "评估完成";}
}

3.3. ✅ 总结:外观模式实现要点

步骤

内容

把多个子系统服务拆分成独立类

建立一个 Facade

类,对外暴露统一方法

客户端只与 Facade

类交互

④(Spring)

把子系统类交给 Spring 管理,外观类通过注入协调调用

4. 外观设计模式适合场景

4.1. ✅ 适合使用外观设计模式的场景

场景

说明

系统结构复杂

系统由多个子系统组成,接口调用复杂,客户端需要简化调用流程。

统一访问入口

需要为多个子系统提供一个统一的接口,客户端只需调用这个接口。

分层架构设计

在分层架构中,用外观层屏蔽底层子系统的实现细节,降低耦合度。

系统重构与迁移

需要将旧系统接口封装,兼容老旧代码,逐步迁移到新系统。

多子系统协调

需要协调多个子系统的调用顺序或组合调用逻辑,外观类负责协调。

4.2. ❌ 不适合使用外观设计模式的场景

场景

原因

系统简单

业务流程简单,接口调用不复杂,使用外观反而增加额外层次和复杂度。

单一功能模块

只涉及一个功能模块,没有必要额外封装统一接口。

频繁变动接口

子系统接口频繁改变,外观层也需频繁修改,维护成本高。

业务高度耦合

业务流程需要客户端灵活控制子系统内部调用,外观隐藏细节不合适。

5. 外观设计模式实战示例

下面是一个金融风控场景下的外观设计模式实战示例,演示如何用Spring管理所有对象,并且使用注解方式注入,避免构造函数注入,方便集成和维护。

5.1. 项目背景

风控系统需要对用户进行风险评估,涉及多个子系统服务:

  • 黑名单查询服务(BlacklistService)
  • 风险分数计算服务(RiskScoreService)
  • 信用额度服务(CreditService)

通过外观模式(RiskFacade)统一暴露给业务调用层,隐藏各子系统复杂调用。

5.2. 子系统服务类

@Service
public class BlacklistService {public boolean isBlacklisted(String userId) {System.out.println("检查用户是否在黑名单中...");// 模拟黑名单检查逻辑return "blacklistedUser".equals(userId);}
}@Service
public class RiskScoreService {public int calculateRiskScore(String userId) {System.out.println("计算用户风险分数...");// 模拟风险分数计算return 80;}
}@Service
public class CreditService {public int getCreditLimit(String userId) {System.out.println("获取用户信用额度...");// 模拟信用额度查询return 10000;}
}

5.3. 外观类

@Component
public class RiskFacade {@Autowiredprivate BlacklistService blacklistService;@Autowiredprivate RiskScoreService riskScoreService;@Autowiredprivate CreditService creditService;public void assessUserRisk(String userId) {System.out.println("=== 开始风控评估 ===");if (blacklistService.isBlacklisted(userId)) {System.out.println("用户 " + userId + " 在黑名单中,拒绝服务!");return;}int riskScore = riskScoreService.calculateRiskScore(userId);int creditLimit = creditService.getCreditLimit(userId);System.out.println("用户 " + userId + " 风险分数: " + riskScore);System.out.println("用户 " + userId + " 信用额度: " + creditLimit);System.out.println("=== 评估结束 ===");}
}

5.4. Controller 层示例

@RestController
@RequestMapping("/risk")
public class RiskController {@Autowiredprivate RiskFacade riskFacade;@GetMapping("/assess")public String assessRisk(@RequestParam String userId) {riskFacade.assessUserRisk(userId);return "风控评估完成";}
}

说明

  • 所有类都由Spring管理,使用 @Service@Component 注解。
  • 外观类 RiskFacade 注入所有子系统服务,作为统一调用入口。
  • 业务层(Controller)只调用外观类接口,避免直接依赖多个子系统。
  • 避免构造函数注入,使用 @Autowired 注解实现自动注入,符合你的要求。

6. 外观设计模式思考

6.1. 门面设计设计模式和DDD中Facade设计区别

6.1.1. 门面设计模式(Facade Pattern)

  • 定义:Facade 是一种结构型设计模式,用于为复杂的子系统提供一个简化的统一接口。它屏蔽了系统的复杂性,客户端通过门面类与子系统交互,而无需直接了解子系统的实现细节。
  • 关注点:简化接口,降低客户端与子系统之间的耦合。
  • 典型用途
    • 为一个复杂系统提供统一的入口。
    • 隐藏子系统的内部复杂逻辑。
    • 提高客户端调用的便利性。

6.1.2. DDD 中的 Facade

  • 定义:在领域驱动设计中,Facade 是一个用于协调多个领域对象领域服务的接口或类。它通常用于应用层,作为应用服务的一部分,负责将客户端的请求转化为对领域层的调用。
  • 关注点:隔离应用层与领域层,简化应用层与外部系统(如 UI、接口调用等)的交互。
  • 典型用途
    • 在应用层对外暴露接口。
    • 封装复杂的领域操作,协调多个领域对象和领域服务。
    • 承载用例(Use Case)的实现逻辑。

6.1.3. 核心区别

维度

门面设计模式(Facade Pattern)

DDD 中的 Facade

目的

为复杂子系统提供一个统一、简化的接口,屏蔽系统内部实现细节。

为外部系统(如 UI 层、API 层)提供对领域层的调用接口。

适用范围

用于封装技术组件(子系统、模块、服务)。

用于封装领域逻辑,暴露领域行为。

位置

通常在技术实现层,用于协调多个技术模块。

通常在应用层,调用领域层服务或聚合根。

关注点

简化客户端调用,隐藏子系统复杂性。

承载用例逻辑,协调领域对象和服务,实现业务需求。

是否直接操作领域

通常不直接操作领域对象,只封装系统内部的模块调用。

直接操作领域对象、聚合根、领域服务等。

6.2. 门面设计模式(Facade Pattern)的设计思想与 Application 层 的职责相似

6.2.1. Application层与门面模式(Facade Pattern)

Application 层(DDD 中的角色)

  • 主要职责
    • 提供用例逻辑(Use Case)服务。
    • 负责协调领域层(Domain Layer)的多个领域对象、领域服务和聚合根。
    • 为外部系统(如 API 层、UI 层等)提供统一的调用接口。
    • 不包含业务逻辑,业务逻辑属于领域层,它仅负责调度领域逻辑
  • 核心思想
    • 简化外部调用(UI 层或 API 层)对复杂领域逻辑的访问。
    • 将应用层与领域层隔离,保证领域层专注于业务规则,而应用层处理系统操作的组合与流程。

门面模式(Facade Pattern)

  • 主要职责
    • 为子系统提供一个统一接口,屏蔽系统内部的复杂性。
    • 将多个子系统或模块的调用逻辑封装在一个类中,外部调用方无需了解子系统的细节。
    • 简化客户端调用,降低外部代码与子系统的耦合度。
  • 核心思想
    • 提供一个简化的、高层次的接口来调用内部复杂的逻辑或子系统。

6.2.2. Application 层和门面模式的相似性

维度

Application 层

门面模式(Facade Pattern)

主要职责

调用领域层的对象和服务,为外部系统提供统一的调用接口。

调用子系统的服务,为客户端提供简化的调用入口。

目标

简化外部系统对复杂领域逻辑的调用,协调领域服务和对象。

隐藏子系统的复杂性,为客户端提供简化的接口。

隐藏复杂性

隐藏领域层内部对象之间的交互细节。

隐藏子系统之间的交互细节。

调用方

外部系统(UI 层、API 层等)。

客户端或其他模块。

实现的粒度

业务用例为单位,封装一个完整的应用逻辑。

技术组件为单位,封装多个模块或服务的调用逻辑。

结论两者都承担了“简化复杂性、统一接口”的职责,但 Application 层更专注于领域逻辑的编排和业务用例,而门面模式更关注技术子系统的整合和封装。

6.3. 项目提供RPC 服务接口设计是不是属于的门面设计模式?

是的,可以认为属于门面设计模式的应用。门面模式(Facade Pattern)定义:为子系统中的一组复杂接口提供一个统一的高层接口,使子系统更易使用。

Spring 中 RPC 服务接口的特点(比如基于 Dubbo、gRPC、Spring Cloud):

特性

说明

📦 对外暴露服务接口

Controller 不再是主角,RPC 接口才是系统“对外的门面”

⚙️ 封装多个底层业务服务(Service、DAO、组件等)

对外提供统一调用入口

🔐 对调用者隐藏实现细节

调用方只知道接口,内部逻辑对其不可见

🔌 提供远程访问能力

一般用于微服务、分布式系统间通信

这与门面模式的核心思想高度一致:对复杂子系统提供统一、简洁、高层次的访问接口。示例场景:

假设你有一个贷款审批系统,下游调用方只需调用如下 RPC 接口:

public interface LoanApprovalRpcService {ApprovalResult approveLoan(LoanApplicationDTO application);
}

而这个接口内部其实会:

  • 校验申请数据
  • 调用风控系统
  • 查询信用评分
  • 写入审批日志
  • 通知第三方平台

这时你暴露的这个 RPC 接口就非常标准地扮演了“门面”角色

  • 对外隐藏了所有复杂的逻辑
  • 调用者只需要关心一个方法:approveLoan(...)

博文参考

  • 4. 外观模式 — Graphic Design Patterns
  • 外观设计模式(门面模式)

http://www.hkcw.cn/article/HAeKPoQXip.shtml

相关文章

计算机网络(5)——数据链路层

1.概述 数据链路层负责一套链路上从一个节点向另一个物理链路直接相连的相邻节点传输数据报。换言之,主要解决相邻节点间的可靠数据传输 节点(nodes):路由器和主机 链路(links):连接相邻节点的通信信道 2.数据链路层服务 2.1 组帧 组帧(fra…

深度优先搜索(DFS)邻接矩阵实现

代码&#xff1a; // 访问标记数组&#xff0c;需要提前初始化为false bool visited[MAX_VERTEX_NUM]; void DFS(AMGraph G, int v) { // 图G为邻接矩阵类型&#xff0c;v是当前访问的顶点// 步骤1&#xff1a;访问顶点vcout << v; // 输出顶点编号…

将手机网络经USB数据线和本地局域网共享给华为AP6050DN无线接入点

引言 由于最近装毕的新家所在的小区未能及时通宽带,于是家中各类无线设备如何上网就成了首要要解决的问题。 鉴于家中要联网的设备多、类型杂、支持频段也不一,总是开手机热点不是回事儿,于是就想着把手机网络引至华为AP6050DN无线接入点中,让家中所有的无线设备都能快速高…

接口安全SOAPOpenAPIRESTful分类特征导入项目联动检测

1 、 API 分类特征 SOAP - WSDL OpenApi - Swagger RESTful - /v1/api/ 2 、 API 常见漏洞 OWASP API Security TOP 10 2023 3 、 API 检测流程 接口发现&#xff0c;遵循分类&#xff0c;依赖语言&#xff0c; V1/V2 多版本等 Method &#xff1a;请求方法 攻击方…

Python基础:常量、变量、变量类型、表达式、注释、输入输入、运算符

引言 手把手带你快速上手Python 一、常量和表达式 在Python中运行下面的代码&#xff1a; print(1 2 - 3) print(1 2 * 3) print(1 2 / 3)​​​​ 注意: print 是一个 Python 内置的 函数, 这个稍后详细介绍.可以使用 - * / ( ) 等运算符进行算术运算. 先算乘除, 后算加…

归一化相关

归一化相关问题 归一化方式Batch NormalizationLayer NormalizationInstance NormalizationGroup NormalizationRMSNorm(Root Mean Square Layer Normalization):RMSNorm 和 LayerNorm区别?归一化方式 Batch Normalization 在每一层的输入进行归一化处理,使其在每个批次内…

进阶日记(一)—LLMs本地部署与运行(更新中)

本项目资料主要来源&#xff1a;【知识科普】【纯本地化搭建】【不本地也行】DeepSeek RAGFlow 构建个人知识库_哔哩哔哩_bilibili 目录 一、背景知识 二、Ollma安装 三、Docker安装 接上一篇&#xff08;非科班大模型工程师进阶日记&#xff08;〇&#xff09;&#xff…

【论文解读】Deformable DETR | Deformable Transformers for End-to-End Object Detection

论文地址&#xff1a;https://arxiv.org/pdf/2010.04159 代码地址&#xff1a;https://github.com/fundamentalvision/Deformable-DETR 摘要 DETR最近被提出&#xff0c;旨在消除物体检测中许多手工设计的组件的需求&#xff0c;同时展示出良好的性能。然而&#xff0c;由于T…

大语言模型的推理能力

2025年&#xff0c;各种会推理的AI模型如雨后春笋般涌现&#xff0c;比如ChatGPT o1/o3/o4、DeepSeek r1、Gemini 2 Flash Thinking、Claude 3.7 Sonnet (Extended Thinking)。 对于工程上一些问题比如复杂的自然语言转sql&#xff0c;我们可能忍受模型的得到正确答案需要更多…

PINN for PDE(偏微分方程)1 - 正向问题

PINN for PDE(偏微分方程)1 - 正向问题 目录 PINN for PDE(偏微分方程)1 - 正向问题一、什么是PINN的正问题二、求解的实际例子三、基于Pytorch实现的代码 - 分解3.1 引入库函数3.2 设置超参数3.3 设计随机种子&#xff0c;确保复现结果的一致性3.4 对于条件等式生成对应的训练…

Adobe LiveCycle ES、LiveCycle DS 与 BlazeDS 关系解析与比较

Adobe LiveCycle 系列产品是企业级解决方案的重要组成部分&#xff0c;但在命名和功能上常常造成混淆。 产品定义 Adobe LiveCycle ES (Enterprise Suite) LiveCycle ES是一个基于SOA的平台&#xff0c;部署在J2EE应用服务器上。它提供开发、部署、配置和执行服务的功能。基…

Redis最佳实践——性能优化技巧之监控与告警详解

Redis 在电商应用的性能优化技巧之监控与告警全面详解 一、监控体系构建 1. 核心监控指标矩阵 指标类别关键指标计算方式/说明健康阈值&#xff08;参考值&#xff09;内存相关used_memoryINFO Memory 获取不超过 maxmemory 的 80%mem_fragmentation_ratio内存碎片率 used_m…

使用 DeepSeek API 搭建智能体《无间》- 卓伊凡的完整指南 -优雅草卓伊凡

使用 DeepSeek API 搭建智能体《无间》- 卓伊凡的完整指南 -优雅草卓伊凡 作者&#xff1a;卓伊凡 前言&#xff1a;为什么选择 DeepSeek API&#xff0c;而非私有化部署&#xff1f; 在开始搭建智能体之前&#xff0c;我想先说明 为什么推荐使用 DeepSeek API&#xff0c;而…

lidar和imu的标定(三)平面约束的方法

看了一篇&#xff1a;基于平面特征的地面机器人雷达-惯性里程计外参标定方法&#xff1b; 它和GRIL-Calib不同之处&#xff0c;就是采用了平面优化和栅格优化。 栅格优化就不介绍了&#xff0c;感觉工程上不。 平面优化则很容易懂&#xff0c;就是标定出来了激光雷达到IMU之…

CppCon 2014 学习: C++ on Mars

主要介绍了如何在火星探测器的飞行软件中使用 C。&#xff1a; 介绍了火星探测器&#xff08;如 Sojourner, Spirit, Opportunity, Curiosity, Perseverance&#xff09;。强调其复杂性和自主性。 延迟的现实&#xff1a;地球与火星之间的通信时延 单程信号延迟为 4 到 22 分…

【MFC】初识MFC

目录 01 模态和非模态对话框 02 静态文本 static text 01 模态和非模态对话框 首先我们需要知道模态对话框和非模态对话框的区别&#xff1a; 模态对话框是一种阻塞时对话框&#xff0c;它会阻止用户与应用程序的其他部分进行交互&#xff0c;直到用户与该对话框进行交互并关…

C#数字图像处理(二)

文章目录 1.灰度直方图1.1 灰度直方图定义1.2 灰度直方图编程实例 2.线性点运算2.1线性点运算定义2.2 线性点运算编程实例 3.全等级直方图灰度拉伸3.1 灰度拉伸定义3.2 灰度拉伸编程实例 4.直方图均衡化4.1 直方图均衡化定义4.2 直方图均衡化编程实例 5.直方图匹配5.1 直方图匹…

SOC-ESP32S3部分:24-WiFi配网

飞书文档https://x509p6c8to.feishu.cn/wiki/OD4pwTE8Jift2IkYKdNcSckOnfd 对于WiFi类设备&#xff0c;最重要的功能之一就是联网&#xff0c;WiFi需要联网&#xff0c;就需要知道我们家里路由的账号和密码&#xff0c;像手机类型的高端设备没什么问题&#xff0c;我们可以直接…

使用langchain实现五种分块策略:语义分块、父文档分块、递归分块、特殊格式、固定长度分块

文章目录 分块策略详解1. 固定长度拆分&#xff08;简单粗暴&#xff09;2. 递归字符拆分&#xff08;智能切割&#xff09;3. 特殊格式拆分&#xff08;定向打击&#xff09;Markdown分块 4. 语义分割&#xff08;更智能切割&#xff09;基于Embedding的语义分块基于模型的端到…

(七)【Linux进程的创建、终止和等待】

1 进程创建 1.1 在谈fork函数 #include <unistd.h> // 需要的头文件// 返回值&#xff1a;子进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1调用fork函数后&#xff0c;内核做了下面的工作&#xff1a; 创建了一个子进程的PCB结构体、并拷贝一份相…