当前位置: 首页 > article >正文

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

摘要

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

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.lryc.cn/news/2395633.html

相关文章:

  • Linux `vi/vim` 编辑器深度解析与高阶应用指南
  • ES中must与filter的区别
  • qt之开发大恒usb3.0相机三
  • Transformer架构详解:从Attention到ChatGPT
  • 数据中台(大数据平台)之数据安全管理
  • github双重验证密码忘记或者获取不了了怎么办
  • 告别复杂操作!电脑极简风格计时使用
  • stm32cube ide如何将工具链替换成arm-none-eabi-gcc
  • [STM32问题解决(2)]STM32通过串口与PC通信,打开串口助手后无法在打开状态下下载程序和复位STM32
  • RabbitMQ 与其他 MQ 的对比分析:Kafka/RocketMQ 选型指南(二)
  • OpenHarmony定制系统组合按键(一)
  • ORDER BY子句在一个 SQL 查询中只能出现一次
  • Spring Boot 3 整合 MQ 构建聊天消息存储系统
  • DeepSeek实战:打造智能数据分析与可视化系统
  • 非线性声学计算与强化学习融合框架:突破复杂环境人机交互的新技术
  • C++ - STL #什么是STL #STL的版本 #闭源开源 #STL的六大组件
  • Flutter - 原生交互 - 相机Camera - 01
  • 湖北理元理律师事务所:个人债务管理的温度与精度
  • Compose原理 - 整体架构与主流程
  • 从0开始学vue:实现一个简单页面
  • 在机器视觉测量和机器视觉定位中,棋盘格标定如何影响精度
  • CppCon 2014 学习: C++ Test-driven Development
  • RAGflow详解及实战指南
  • JWT 不对外,Session ID 对外:构建安全可控的微服务认证架构
  • [Godot] 如何导出安卓 APK 并在手机上调试
  • React 路由管理与动态路由配置实战
  • ZYNQ sdk lwip配置UDP组播收发数据
  • 11.21 LangGraph多轮对话系统实战:三步构建高效信息整理引擎,效率提升300%!
  • 高光谱成像相机:基于高光谱成像技术的玉米种子纯度检测研究
  • Linux《文件系统》