设计模式——Facade(门面)设计模式

摘要

本文介绍了外观设计模式,这是一种通过简单接口封装复杂系统的设计模式。它简化了客户端与子系统之间的交互,降低了耦合度,并提供了统一的调用接口。文章还探讨了该模式的优缺点,并提供了类图实现和使用场景。

1. 外观设计模式是什么

外观模式的核心思想:用一个简单的接口来封装一个复杂的系统,使这个系统更容易使用。与DDD思想中application 有相似之处。

1.1. 外观设计模式作用

  1. 简化访问:门面模式通过提供一个简单的接口,将复杂的子系统封装起来。外部不需要了解子系统的内部逻辑,只需要通过门面类与子系统交互。
  2. 降低耦合:门面模式可以减少外部代码与子系统之间的依赖性。如果子系统发生了变化,只需要调整门面类即可,外部代码无需修改。

1.2. 外观设计模式优缺点

优点:

  • 实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端。
  • 简化了客户端对子系统的使用难度,客户端(用户)无须关心子系统的具体实现方式,而只需要和外观进行交互即可。
  • 为不同的用户提供了统一的调用接口,方便了系统的管理和维护。

缺点:

  • 因为统一了调用的接口,降低了系统功能的灵活性。

2. 外观设计模类图实现

2.1. 外观设计类模型

外观模式是最简单的设计模式之一,只有两个角色。

  1. 外观角色(Facade): 为子系统封装统一的对外接口,如同子系统的一个门面。这个类一般不负责具体的业务逻辑,只是一个委托类,具体的业务逻辑由子系统完成。
  2. 子系统(SubSystem): 由多个类组成的具有某一特定功能的子系统。可以是第三方库,也可以是自己的基础库,还可能是一个子服务,为整个系统提供特定的功能或服务。

在软件的层次化结构设计中可以使用外观模式来定义每一层系统的调用接口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。这时就会有如下这样的层次结构图:

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

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

定义:Facade 是一种结构型设计模式,用于为复杂的子系统提供一个简化的统一接口。它屏蔽了系统的复杂性,客户端通过门面类与子系统交互,而无需直接了解子系统的实现细节。

关注点:简化接口,降低客户端与子系统之间的耦合。

典型用途

  • 为一个复杂系统提供统一的入口。
  • 隐藏子系统的内部复杂逻辑。
  • 提高客户端调用的便利性。

2.2.2. DDD 中的 Facade层

定义:在领域驱动设计中,Facade 是一个用于协调多个领域对象领域服务的接口或类。它通常用于应用层,作为应用服务的一部分,负责将客户端的请求转化为对领域层的调用。

关注点:隔离应用层与领域层,简化应用层与外部系统(如 UI、接口调用等)的交互。

典型用途

  • 在应用层对外暴露接口。
  • 封装复杂的领域操作,协调多个领域对象和领域服务。
  • 承载用例(Use Case)的实现逻辑。

2.2.3. 核心区别

维度

门面设计模式(Facade Pattern)

DDD 中的 Facade

目的

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

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

适用范围

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

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

位置

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

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

关注点

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

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

是否直接操作领域

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

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

2.3. 门面设计模式的设计思想与Application层的职责相似

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

Application 层(DDD 中的角色)

主要职责

  • 提供用例逻辑(Use Case)服务。
  • 负责协调领域层(Domain Layer)的多个领域对象、领域服务和聚合根。
  • 为外部系统(如 API 层、UI 层等)提供统一的调用接口。
  • 不包含业务逻辑,业务逻辑属于领域层,它仅负责调度领域逻辑

核心思想

  • 简化外部调用(UI 层或 API 层)对复杂领域逻辑的访问。
  • 将应用层与领域层隔离,保证领域层专注于业务规则,而应用层处理系统操作的组合与流程。

门面模式(Facade Pattern)

主要职责

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

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

维度

Application 层

门面模式(Facade Pattern)

主要职责

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

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

目标

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

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

隐藏复杂性

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

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

调用方

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

客户端或其他模块。

实现的粒度

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

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

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

3. 外观设计模式使用场景

3.1. 为复杂系统提供简化接口

场景:当一个系统由多个模块、子系统组成,客户端需要与这些模块进行交互时,如果直接调用底层模块,会导致客户端逻辑复杂且高度耦合。

示例:视频转码系统包含解码模块、转码模块、压缩模块等多个子系统,通过一个 VideoProcessingFacade 统一接口简化调用流程。

@Service
public class VideoProcessingFacade {@Autowiredprivate Decoder decoder;@Autowiredprivate Transcoder transcoder;@Autowiredprivate Compressor compressor;public void processVideo(String filePath) {decoder.decode(filePath);transcoder.transcode();compressor.compress();}
}

应用场景:客户端只需要调用 processVideo,无需关心底层模块的具体调用顺序和逻辑。

3.2. 隔离与第三方库的耦合

场景:当项目需要调用第三方库(如支付网关、短信服务、邮件服务等)时,如果直接使用其 API,可能会导致代码和第三方库耦合,增加维护成本。使用门面模式可以隔离这种耦合。

示例:支付服务集成不同的支付网关(如 PayPal、Stripe、支付宝)。

@Service
public class PaymentFacade {@Autowiredprivate PayPalService payPalService;@Autowiredprivate StripeService stripeService;public void payWithPayPal(String account, double amount) {payPalService.processPayment(account, amount);}public void payWithStripe(String account, double amount) {stripeService.processPayment(account, amount);}
}

应用场景:客户端只需要知道 PaymentFacade 提供的支付接口,而无需了解具体的支付网关实现。

3.3. 封装遗留系统

场景:当需要对接一个遗留系统(Legacy System),但不想直接暴露遗留系统复杂或低效的接口时,可以使用门面模式封装其调用。

示例:一个遗留的客户信息管理系统接口复杂,可以通过门面模式提供统一的接口。

@Service
public class CustomerServiceFacade {@Autowiredprivate LegacyCustomerService legacyService;public Customer getCustomerDetails(String customerId) {return legacyService.getCustomerData(customerId);}
}

应用场景:客户端通过门面接口获取客户信息,无需直接与遗留系统交互。

3.4. 提供多个子系统的统一入口

  • 场景:在系统中有多个子系统需要协调工作,且需要对外暴露一个简化的统一接口。
  • 示例:智能家居系统包含灯光控制、空调控制、音响控制等模块,可以通过一个 SmartHomeFacade 提供统一入口。
@Service
public class SmartHomeFacade {@Autowiredprivate LightController lightController;@Autowiredprivate AirConditionerController airConditionerController;@Autowiredprivate MusicController musicController;public void startEveningMode() {lightController.dimLights();airConditionerController.setTemperature(22);musicController.playRelaxingMusic();}
}

3.5. 简化复杂工作流

场景:当一个复杂的业务流程需要多个步骤完成(如订单处理、物流系统对接等),可以使用门面模式封装工作流的实现。

示例:订单处理系统需要完成库存检查、支付处理、物流创建等步骤。

@Service
public class OrderFacade {@Autowiredprivate InventoryService inventoryService;@Autowiredprivate PaymentService paymentService;@Autowiredprivate ShippingService shippingService;public void processOrder(String productId, String userId, double amount) {if (inventoryService.checkStock(productId)) {if (paymentService.processPayment(userId, amount)) {shippingService.createShipment(productId, userId);}}}
}

应用场景:简化订单处理流程,客户端只需调用 processOrder 方法即可。

3.6. 提供接口的多版本支持

场景:系统需要对外暴露多个版本的接口时,可以使用门面模式将不同版本的实现封装在同一个接口中。

示例:API 网关根据客户端请求调用不同版本的服务实现。

@Service
public class ApiGatewayFacade {@Autowiredprivate V1Service v1Service;@Autowiredprivate V2Service v2Service;public void callApi(String version, String request) {if ("v1".equals(version)) {v1Service.handleRequest(request);} else if ("v2".equals(version)) {v2Service.handleRequest(request);}}
}

应用场景:统一管理和调用不同版本的服务实现。

3.7. 统一对外暴露接口,隐藏底层实现

场景:当系统需要对外暴露一个简单的公共接口,但不希望外部用户了解底层实现细节时,可以使用门面模式。

示例:企业级服务平台(如 ERP 系统)封装内部模块,统一对外提供服务。

@Service
public class EnterpriseServiceFacade {@Autowiredprivate FinanceService financeService;@Autowiredprivate HRService hrService;public void processPayroll() {financeService.calculateSalaries();hrService.updateEmployeeRecords();}
}

应用场景:对外统一暴露企业服务,无需暴露各模块的具体实现。

3.8. 降低代码的维护复杂性

场景:当系统模块或服务之间的调用关系复杂,未来可能需要更换某些模块的实现时,通过门面模式可以降低修改代码时的风险。

示例:缓存系统封装 Redis 和 Memcached 的实现,后期替换时只需修改门面内部逻辑。

@Service
public class CacheFacade {@Autowiredprivate RedisCache redisCache;@Autowiredprivate MemcachedCache memcachedCache;public void put(String key, String value) {redisCache.put(key, value);memcachedCache.put(key, value);}public String get(String key) {return redisCache.get(key);}
}

4. 外观设计模式示例

假设我们正在设计一个订单处理系统,包含以下子系统:

  1. 库存服务:检查库存是否足够。
  2. 支付服务:处理支付逻辑。
  3. 通知服务:在订单成功后发送通知。

客户端不需要直接与这些子系统交互,而是通过一个门面类来调用这些服务。

4.1. 外观设计模式实现

4.1.1. 子系统类

这些子系统类是具体的服务,它们各自完成自己的职责。

package com.example.subsystems;import org.springframework.stereotype.Component;// 库存服务
@Component
public class InventoryService {public boolean checkStock(String productId) {System.out.println("Checking stock for product: " + productId);return true; // 假设库存充足}
}// 支付服务
@Component
public class PaymentService {public boolean processPayment(String userId, double amount) {System.out.println("Processing payment for user: " + userId + ", amount: " + amount);return true; // 假设支付成功}
}// 通知服务
@Component
public class NotificationService {public void sendNotification(String userId, String message) {System.out.println("Sending notification to user: " + userId + ", message: " + message);}
}

4.1.2. 门面类

门面类封装了对多个子系统的调用逻辑,对外提供一个统一的接口。

package com.example.facade;import com.example.subsystems.InventoryService;
import com.example.subsystems.PaymentService;
import com.example.subsystems.NotificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class OrderFacade {@Autowiredprivate InventoryService inventoryService;@Autowiredprivate PaymentService paymentService;@Autowiredprivate NotificationService notificationService;public boolean processOrder(String userId, String productId, double amount) {// 1. 检查库存if (!inventoryService.checkStock(productId)) {System.out.println("Stock not sufficient for product: " + productId);return false;}// 2. 处理支付if (!paymentService.processPayment(userId, amount)) {System.out.println("Payment failed for user: " + userId);return false;}// 3. 发送通知notificationService.sendNotification(userId, "Order for product " + productId + " has been successfully processed!");System.out.println("Order processing completed successfully!");return true;}
}

4.1.3. 客户端

客户端只需要调用门面类的方法,而不需要关心子系统的具体实现。

package com.example.client;import com.example.facade.OrderFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class OrderClient implements CommandLineRunner {@Autowiredprivate OrderFacade orderFacade;@Overridepublic void run(String... args) {// 模拟处理订单String userId = "user123";String productId = "product456";double amount = 99.99;boolean result = orderFacade.processOrder(userId, productId, amount);if (result) {System.out.println("Order processed successfully!");} else {System.out.println("Order processing failed!");}}
}

博文参考

《软件设计模式》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/483071.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

泷羽sec-burp功能介绍(1) 学习笔记

声明! 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&a…

技术创新与人才培养并重 软通动力子公司鸿湖万联亮相OpenHarmony人才生态大会

11月27日,由开放原子开源基金会指导,OpenHarmony项目群工作委员会主办的OpenHarmony人才生态大会2024在武汉隆重举办。软通动力子公司鸿湖万联作为OpenHarmony项目群A类捐赠人应邀出席。大会期间,鸿湖万联不仅深度参与了OpenHarmony人才生态年…

简单快速的上手python

前言 python是一门可以快速上手的语言,原因是它语法简单,api容易使用自由灵活 当我们需要安装任何的三方库时,只需要执行 pip install XX 之后在代码里面import xxx就可以使用python啦。 并且python的代码自由灵活,使用缩进区…

【算法刷题指南】优先级队列

🌈个人主页: 南桥几晴秋 🌈C专栏: 南桥谈C 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据…

[241129] Docker Desktop 4.36 发布:企业级管理功能、WSL 2 增强 | Smile v4.0.0 发布

目录 Docker Desktop 4.36 发布:企业级管理功能、WSL 2 和 ECI 增强Smile v4.0.0 发布!Java 机器学习库迎来重大升级 Docker Desktop 4.36 发布:企业级管理功能、WSL 2 和 ECI 增强 Docker Desktop 4.36 带来了强大的更新,简化了…

vue3+typescript自定义input组件

官方文档:https://cn.vuejs.org/guide/components/events#%E5%AE%9A%E4%B9%89%E8%87%AA%E5%AE%9A%E4%B9%89%E4%BA%8B%E4%BB%B6 触发与监听事件​ 在组件的模板表达式中,可以直接使用 $emit 方法触发自定义事件 (例如:在 v-on 的处理函数中)…

HarmonyOS4+NEXT星河版入门与项目实战(23)------实现手机游戏摇杆功能

文章目录 1、案例效果2、案例实现1、代码实现2、代码解释4、总结1、案例效果 2、案例实现 1、代码实现 代码如下(示例): import router from @ohos.router import {ResizeDirection } from @ohos.UiTest import curves

Qt的定时器应用案例 || Qt的图片添加显示

目录 1.ui界面 2.头文件 3.cpp源文件 4.main文件 5.关于ui_mytimerevent.h的代码编译错误 6.图片的添加展示方式 7.结果展示 8.参考文章 1.ui界面 2.头文件 #ifndef MYTIMEREVENT_H #define MYTIMEREVENT_H#include <QMainWindow> #include <QTime> //#in…

【大数据学习 | Spark-SQL】关于RDD、DataFrame、Dataset对象

1. 概念&#xff1a; RDD&#xff1a; 弹性分布式数据集&#xff1b; DataFrame&#xff1a; DataFrame是一种以RDD为基础的分布式数据集&#xff0c;类似于传统数据库中的二维表格。带有schema元信息&#xff0c;即DataFrame所表示的二维表数据集的每一列都带有名称和类型…

分布式集群下如何做到唯一序列号

优质博文&#xff1a;IT-BLOG-CN 分布式架构下&#xff0c;生成唯一序列号是设计系统常常会遇到的一个问题。例如&#xff0c;数据库使用分库分表的时候&#xff0c;当分成若干个sharding表后&#xff0c;如何能够快速拿到一个唯一序列号&#xff0c;是经常遇到的问题。实现思…

53 基于单片机的8路抢答器加记分

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 首先有三个按键 分别为开始 暂停 复位&#xff0c;然后八个选手按键&#xff0c;开机显示四条杠&#xff0c;然后按一号选手按键&#xff0c;数码管显示&#xff13;&#xff10;&#xff0c;这…

深度学习基础03_BP算法(下)过拟合和欠拟合

目录 一、BP算法(下) 0、反向传播代码回顾 写法一&#xff1a; 写法二(更常用)&#xff1a; 1、BP中的梯度下降 1.数学描述 2.传统下降方式 3.优化梯度下降方式 指数加权平均 Momentum AdaGrad RMSProp Adam(常用) 总结 二、过拟合和欠拟合 1、概念 1.过拟合 …

WPF+MVVM案例实战与特效(三十)- 封装一个系统日志显示控件

文章目录 1、运行效果2、日志控件封装1、文件创建2、DisplayLogPanel.xaml 代码3、DisplayLogPanel.cs 代码4、数据模型5、枚举类型3、自定义控件使用1、LogPanelWindow.xaml2、LogPanelViewModel.cs4、总结1、运行效果 2、日志控件封装 1、文件创建 打开 Wpf_Examples ,在 …

Ubuntu 20.04 Server版连接Wifi

前言 有时候没有网线口插网线或者摆放电脑位置不够时&#xff0c;需要用Wifi联网。以下记录Wifi联网过程。 环境&#xff1a;Ubuntu 20.04 Server版&#xff0c;无UI界面 以下操作均为root用户&#xff0c;如果是普通用户&#xff0c;请切换到root用户&#xff0c;或者在需要权…

计算机网络:IP协议详细讲解

目录 前言 一、IP网段划分 二、IP报头 三、解决IP地址不足-->NAT技术 前言 在之前&#xff0c;我们学习了传输层中的TCP和UDP&#xff0c;重点是TCP协议&#xff0c;他帮我们解决具体到主机的哪个应用&#xff08;端口&#xff09;、传输的可靠&#xff08;序列号、校验和…

基于大数据python 电商数据分析及推荐可视化系统(源码+LW+部署讲解+数据库+ppt)

&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 很对人不知道选题怎么选 不清楚自己适合做哪块内容 都可以免费来问我 避免后期給自己答辩找麻烦 增加难度&#xff08;部分学校只有一次答辩机会 没弄好就延迟…

三种方式(oss、本地、minio)图片的上传下载

一、OSS 1、前期准备 1.1 注册阿里云账号&#xff0c;开启对象存储oss功能&#xff0c;创建一个bucket&#xff08;百度教程多的是&#xff0c;跟着创建一个就行&#xff0c;创建时注意存储类型是标准存储&#xff0c;读写权限是公共读&#xff09; 有的在创建桶时读写属性是…

Z2400032基于Java+Mysql+SSM的校园在线点餐系统的设计与实现 代码 论文

在线点餐系统 1.项目描述2. 技术栈3. 项目结构后端前端 4. 功能模块5. 项目实现步骤注意事项 6.界面展示7.源码获取 1.项目描述 本项目旨在开发一个校园在线点餐系统&#xff0c;通过前后端分离的方式&#xff0c;为在校学生提供便捷的餐厅点餐服务&#xff0c;同时方便餐厅和…

【前端】理解 JavaScript 中 typeof 操作符的独特行为

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端 文章目录 &#x1f4af;前言&#x1f4af;typeof 操作符的基本使用&#x1f4af;为什么 typeof 数组是 "object"&#xff1f;&#x1f4af;为什么 typeof {} 返回 "object"&#xff1f;&…

Github提交Pull Request教程 Git基础扫盲(零基础易懂)

1 PR是什么&#xff1f; PR&#xff0c;全称Pull Request&#xff08;拉取请求&#xff09;&#xff0c;是一种非常重要的协作机制&#xff0c;它是 Git 和 GitHub 等代码托管平台中常见的功能&#xff0c;被广泛用于参与社区贡献&#xff0c;从而促进项目的发展。 PR的整个过…