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

news2024/12/12 21:55:27

摘要

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

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 {
    @Autowired
    private Decoder decoder;
    @Autowired
    private Transcoder transcoder;
    @Autowired
    private Compressor compressor;

    public void processVideo(String filePath) {
        decoder.decode(filePath);
        transcoder.transcode();
        compressor.compress();
    }
}

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

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

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

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

@Service
public class PaymentFacade {
    @Autowired
    private PayPalService payPalService;
    @Autowired
    private 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 {
    @Autowired
    private LegacyCustomerService legacyService;

    public Customer getCustomerDetails(String customerId) {
        return legacyService.getCustomerData(customerId);
    }
}

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

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

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

    public void startEveningMode() {
        lightController.dimLights();
        airConditionerController.setTemperature(22);
        musicController.playRelaxingMusic();
    }
}

3.5. 简化复杂工作流

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

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

@Service
public class OrderFacade {
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private 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 {
    
    @Autowired
    private V1Service v1Service;
    @Autowired
    private 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 {
    
    @Autowired
    private FinanceService financeService;
    @Autowired
    private HRService hrService;

    public void processPayroll() {
        financeService.calculateSalaries();
        hrService.updateEmployeeRecords();
    }
}

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

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

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

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

@Service
public class CacheFacade {
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private 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 {

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private 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 {

    @Autowired
    private OrderFacade orderFacade;

    @Override
    public 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.coloradmin.cn/o/2253997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

opencv-android编译遇到的相关问题处理

1、opencv-android sdk下载 下载地址:https://opencv.org/releases/ 下载安卓SDK即可 2、解压下载好的SDK 3、导入opencv的SDK到安卓项目中 导入步骤在/OpenCV-android-sdk/sdk/build.gradle文件的注释中写的非常详细,大家可安装官方给出的步骤导入。…

go语言读取yaml配置文件内容

1、config.yaml配置文件内容假设如下 name: "example" version: 1.0 settings:timeout: 30debug: truefeatures:- feature1- feature22、定义结构体 go语言定义结构体匹配yaml内容 package mainimport ("fmt""log""os""gopkg.…

STL算法之其它算法_下

random_shuffle 这个算法将[first,last)的元素次序随机排列。也就说,在N!中可能的元素排列中随机选出一种,此处N为last-first。 N个元素的序列,其排列方式为N!中,random_shuffle会产生一个均匀分布,因此任何一个排列被…

模拟简单的iOT工作流

没有实际接触过iOT的流程,应该实际使用比这个接口返回要复杂,只是演示~希望能参与实际的接口接入,而不是只展示个假数据。 启动RabbitQ 使用的是3.8.5 启动命令 RabbitMQ Service - start RabbitMQ Command Prompt rabbitmqctl start_app …

【快速入门 LVGL】-- 1、STM32 工程移植 LVGL

目录 一、LVGL 简述 二、复制一个STM32工程 三、下载 LVGL 四、裁剪 源文件 五、工程添加 LVGL 文件 六、注册 显示 七、注册 触摸屏 八、LVGL 心跳、任务刷新 九、开跑 LVGL 十、控件的事件添加、响应处理 十 一、几个好玩小事情 十 二、显示中文 ~~ 约定 ~~ 在…

关于线扫相机的使用和注意事项

引言 线扫相机作为工业视觉系统中的核心设备之一,以其高分辨率和高速成像的特点被广泛应用于印刷质量检测、电子元件检测、纺织品缺陷检测等领域。本文从线扫相机的基本原理出发,探讨其使用方法,并总结在实际应用中的注意事项,为…

MybatisPlus字段类型处理器TypeHandler

个人博客:无奈何杨(wnhyang) 个人语雀:wnhyang 共享语雀:在线知识共享 Github:wnhyang - Overview 简介 官网:字段类型处理器 在 MyBatis 中,类型处理器(TypeHandle…

c++编译版本问题#error C++17 or later compatible compiler is required to use xx

问题解决方向 网上多数给出的解决方法是找到setup.py,然后修改extra_compile_args参数中的cxx,由-stdc14改为-stdc17,但是这个方法在我这里没用。 所以我重新理解了下这个error,应该是说为了编译安装当前的库,需要的…

【AI大模型】大型语言模型LLM基础概览:技术原理、发展历程与未来展望

目录 🍔 大语言模型 (LLM) 背景 🍔 语言模型 (Language Model, LM) 2.1 基于规则和统计的语言模型(N-gram) 2.2 神经网络语言模型 2.3 基于Transformer的预训练语言模型 2.4 大语言模型 🍔 语言模型的评估指标 …

一文理解多模态大语言模型——下

作者:Sebastian Raschka 博士, 翻译:张晶,Linux Fundation APAC Open Source Evangelist 编者按:本文并不是逐字逐句翻译,而是以更有利于中文读者理解的目标,做了删减、重构和意译&#xff0c…

uC/OSII学习笔记(二)任务的堆栈检验

加入OSTaskCreateExt()创建拓展任务函数的使用。 加入OSTaskStkChk()堆栈检验函数的使用。 堆栈检验函数可检查任务堆栈的使用字节数量和空闲字节数量。 具体使用方法如下: 1.创建拓展任务OSTaskCreateExt()用于堆栈检验,堆栈检验必须用拓展任务OSTaskCr…

WPF+LibVLC开发播放器-进度条显示和拖动控制

进度条显示和拖动控制 视频教程界面上代码实现进度条显示进度进度条拖动视频进度 效果 视频教程 WPFLibVLC开发播放器-进度条控制 界面上 界面上线增加一个Slider控件&#xff0c;当做播放进度条 <SliderName"PlaySlider"Grid.Row"1"Width"800&qu…

【Rust WebAssembly 入门实操遇到的问题】

Rust WebAssembly 入门实操遇到的问题 什么是WebAssembly跟着教程走wasm-pack build error总结 什么是WebAssembly WebAssembly&#xff08;简称Wasm&#xff09;是一种基于堆栈的虚拟机的二进制指令 格式。Wasm 被设计为编程语言的可移植编译目标&#xff0c;支持在 Web 上部…

同为科技(TOWE)柔性定制化PDU插座

随着科技的进步&#xff0c;越来越多的精密电子设备&#xff0c;成为工作生活密不可分的工具。 电子电气设备的用电环境也变得更为复杂&#xff0c;所以安全稳定的供电是电子电气设备的生命线。 插座插排作为电子电气设备最后十米范围内供配电最终核心部分&#xff0c;便捷、安…

AI RPA 影刀基础教程:开启自动化之旅

RPA 是什么 RPA 就是机器人流程自动化&#xff0c;就是将重复的工作交给机器人来执行。只要是标准化的、重复的、有逻辑行的操作&#xff0c;都可以用 RPA 提效 准备 安装并注册影刀 影刀RPA - 影刀官网 安装 Chrome 浏览器 下载链接&#xff1a;Google Chrome 网络浏览器 …

HTTP 长连接(HTTP Persistent Connection)简介

HTTP长连接怎么看&#xff1f; HTTP 长连接&#xff08;HTTP Persistent Connection&#xff09;简介 HTTP 长连接&#xff08;Persistent Connection&#xff09;是 HTTP/1.1 的一个重要特性&#xff0c;它允许在一个 TCP 连接上发送多个 HTTP 请求和响应&#xff0c;而无需为…

VS与SQL Sever(C语言操作数据库)

作者这里使用的是程序是&#xff1a; Visual Studio SQL Sever (1 对VS的操作 1.首先我们打开Visual Studio Installer&#xff0c;并以管理员身份运行 2.点击修改 3.先选择数据存储和处理&#xff0c;再在右方添加处理工具&#…

基于“开源 2+1 链动 O2O 商城小程序”的门店拉新策略与流程设计

摘要&#xff1a;在数字化商业浪潮席卷之下&#xff0c;实体门店面临着激烈的市场竞争&#xff0c;如何高效拉新成为关乎门店生存与发展的关键问题。本文聚焦于“开源 21 链动 O2O 商城小程序”&#xff0c;深入探讨结合多种手段的门店拉新策略及详细流程设计。通过剖析到店扫码…

微服务即时通讯系统(5)用户管理子服务,网关子服务

用户管理子服务&#xff08;user文件&#xff09; 用户管理子服务也是这个项目中的一个业务最多的子服务&#xff0c;接口多&#xff0c;但是主要涉及的数据表只有user表&#xff0c;Redis的键值对和ES的一个搜索引擎&#xff0c;主要功能是对用户的个人信息进行修改管理&#…

ceph的存储池管理

1 查看存储池信息 查看存储池的名称 [rootceph141ceph]# ceph osd pool ls .mgr查看存储池机器编号 [rootceph141ceph]# ceph osd pool ls 1 .mgr查看存储池的详细信息 [rootceph141ceph]# ceph osd pool ls detail pool 1 .mgr replicated size 3 min_size 2 crush_rule 0 ob…