news 2026/4/18 10:33:13

设计模式学习(12) 23-10 外观模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式学习(12) 23-10 外观模式

文章目录

  • 0.个人感悟
  • 1. 概念
  • 2. 适配场景
    • 2.1 适合的场景
    • 2.2 常见场景举例
  • 3. 实现方法
    • 3.1 实现思路
    • 3.2 UML类图
    • 3.3 代码示例
  • 4. 优缺点
    • 4.1 优点
    • 4.2 缺点
  • 5. 源码分析(MyBatis Configuration为例)

0.个人感悟

  • 外观模式旨在承上启下,对客户端提供一个统一接口,只定义需要关注的操作,对下统筹各个子系统的操作
  • 外观模式很能体现出解耦的一个手段:分层
  • 外观模式有利于理解迪米特法则(最小知道原则)
  • 外观(门面)可以类比web编程中controller,都是对外提供统一的接口,对内整合自己的业务

1. 概念

英文定义(《设计模式:可复用面向对象软件的基础》)

Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

中文翻译

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

理解

  • 外观模式是一种结构型设计模式,它为复杂的子系统提供一个简单易用的接口
  • 通过引入一个外观类(Facade),将客户端与子系统的复杂交互封装起来
  • 外观模式不改变子系统功能,只是提供了一个更易于访问的入口点
  • 实现了客户端与子系统之间的解耦,使子系统更容易维护和扩展
  • 外观模式符合迪米特法则(最少知识原则),客户端只需要与外观类交互

2. 适配场景

2.1 适合的场景

  1. 需要简化复杂子系统接口时,为子系统提供一个统一的入口
  2. 客户端与多个子系统之间存在大量依赖关系,希望降低耦合度
  3. 需要将子系统分层,为每一层提供统一的接口
  4. 系统需要逐步重构,可以先引入外观模式,然后逐步迁移到新系统

2.2 常见场景举例

  • 电脑启动过程:用户只需按下电源键,无需了解BIOS、CPU、内存等组件的复杂交互
  • 数据库连接:JDBC驱动管理器封装了不同数据库的连接细节
  • Web服务接口:REST API网关整合多个微服务的调用
  • 日志框架:SLF4J作为Logback、Log4j等日志实现的外观
  • 支付系统:支付网关整合支付宝、微信支付、银联等不同支付渠道

3. 实现方法

3.1 实现思路

  1. 识别复杂子系统:分析系统中的各个组件和它们之间的依赖关系
  2. 定义外观接口:确定需要为客户端提供的简化操作
  3. 实现外观类:创建外观类,封装子系统的复杂调用逻辑
  4. 客户端通过外观类访问:客户端只与外观类交互,不直接调用子系统
  5. 可选:抽象外观:如果需要支持多个子系统变体,可以引入抽象外观类

3.2 UML类图

角色说明

  • Facade(外观):为子系统提供一个统一的接口,知道哪些子系统负责处理请求
  • Subsystem Classes(子系统类):实现子系统的功能,处理外观对象指派的任务
  • Client(客户端):通过外观接口与子系统交互,不需要了解子系统的内部细节

3.3 代码示例

背景:电脑的启动重启过程,涉及到很多子系统的操作,但是机箱其实只提供了开机关机重启按钮,这就是很典型的外观模式。简化掉bios等流程,代码如下:
各个子系统,简化为CPU 内存 硬盘:

// CPU子系统publicclassCPU{/** * @description 冻结 * @author bigHao * @date 2026/1/12 **/publicvoidfreeze(){System.out.println("CPU冻结当前任务");}/** * @param position 位置 * @description 跳转 * @author bigHao * @date 2026/1/12 **/publicvoidjump(longposition){System.out.println("CPU跳转到内存位置: "+position);}/** * @description 执行 * @author bigHao * @date 2026/1/12 **/publicvoidexecute(){System.out.println("CPU开始执行指令");}}// 内存子系统publicclassMemory{/** * @param position 位置 * @param data 字节数据 * @description // TODO * @author bigHao * @date 2026/1/12 **/publicvoidload(longposition,byte[]data){System.out.println("内存加载数据到位置: "+position);}/** * @description 释放 * @author bigHao * @date 2026/1/12 **/publicvoidshutdown(){System.out.println("内存释放");}}// 硬盘子系统publicclassHardDrive{/** * @param lba 扇区 * @param size 大小 * @return byte[] * @description 读取数据 * @author bigHao * @date 2026/1/12 **/publicbyte[]read(longlba,intsize){System.out.println("硬盘读取扇区 "+lba+",大小: "+size+" bytes");returnnewbyte[size];}/** * @description 释放 * @author bigHao * @date 2026/1/12 **/publicvoidshutdown(){System.out.println("硬盘读释放");}}

外观类,类似于机箱,这里也可以先定义接口,再提供实现

publicclassComputerFacade{// 启动内存地址常量privatestaticfinallongBOOT_ADDRESS=0x7C00;privatestaticfinallongBOOT_SECTOR=0;privatestaticfinalintSECTOR_SIZE=512;privateCPUcpu;privateMemorymemory;privateHardDrivehardDrive;publicComputerFacade(){cpu=newCPU();memory=newMemory();hardDrive=newHardDrive();}/** * @description 启动 * @author bigHao * @date 2026/1/12 **/publicvoidstart(){System.out.println("=== 开始启动计算机 ===\n");// 硬盘加载数据byte[]bootSector=hardDrive.read(BOOT_SECTOR,SECTOR_SIZE);// 加载到内存memory.load(BOOT_ADDRESS,bootSector);// cpu运行cpu.freeze();cpu.jump(BOOT_ADDRESS);cpu.execute();}/** * @description 关机 * @author bigHao * @date 2026/1/12 **/publicvoidshutdown(){System.out.println("=== 开始关闭计算机 ===\n");// cpu停止cpu.freeze();// 内存停止memory.shutdown();// 硬盘停止hardDrive.shutdown();}/** * @description 重启 * @author bigHao * @date 2026/1/12 **/publicvoidrestart(){System.out.println("=== 开始重启计算机 ===\n");start();shutdown();}}

测试:

publicclassClient{staticvoidmain(){// 只用与门面交互ComputerFacadefacade=newComputerFacade();facade.start();facade.shutdown();facade.restart();}}

输出

=== 开始启动计算机 === 硬盘读取扇区 0,大小: 512 bytes 内存加载数据到位置: 31744 CPU冻结当前任务 CPU跳转到内存位置: 31744 CPU开始执行指令 === 开始关闭计算机 === CPU冻结当前任务 内存释放 硬盘读释放 === 开始重启计算机 === === 开始启动计算机 === 硬盘读取扇区 0,大小: 512 bytes 内存加载数据到位置: 31744 CPU冻结当前任务 CPU跳转到内存位置: 31744 CPU开始执行指令 === 开始关闭计算机 === CPU冻结当前任务 内存释放 硬盘读释放

4. 优缺点

4.1 优点

符合高内聚低耦合原则

  • 降低耦合度:将客户端与复杂的子系统解耦,客户端只依赖外观类
  • 提高内聚性:外观类将相关的子系统操作封装在一起
    提高复用性
  • 外观类可以被多个客户端复用,避免重复编写复杂的子系统调用代码
    增强可维护性
  • 子系统内部变化不会影响客户端,只需要修改外观类
  • 便于分层和模块化管理
    提高可读性
  • 简化了客户端代码,使其更加清晰易懂
  • 提供了清晰的系统边界和接口
    符合开闭原则
  • 可以扩展外观类来添加新功能,而不需要修改现有代码

4.2 缺点

可能违反单一职责原则

  • 如果外观类过于庞大,可能承担了太多职责
    性能开销
  • 额外的调用层可能带来轻微的性能损失
    灵活性受限
  • 对于需要访问子系统特定功能的客户端,可能需要绕过外观类

5. 源码分析(MyBatis Configuration为例)

MyBatis中的Configuration类是外观模式的典型应用,它封装了MyBatis框架的复杂配置和初始化过程。
MyBatis Configuration类结构

// Configuration类充当了MyBatis的外观类publicclassConfiguration{// 存储各种配置信息protectedEnvironmentenvironment;protectedTypeAliasRegistrytypeAliasRegistry;protectedTypeHandlerRegistrytypeHandlerRegistry;protectedMapperRegistrymapperRegistry;protectedMap<String,MappedStatement>mappedStatements;protectedMap<String,Cache>caches;// 各种配置方法 - 对外提供简单接口publicvoidaddMappers(StringpackageName){mapperRegistry.addMappers(packageName);}public<T>voidaddMapper(Class<T>type){mapperRegistry.addMapper(type);}publicvoidaddMappedStatement(MappedStatementms){mappedStatements.put(ms.getId(),ms);}publicMappedStatementgetMappedStatement(Stringid){returnmappedStatements.get(id);}// 类型处理器相关方法publicvoidregisterTypeHandler(TypeHandler<?>typeHandler){typeHandlerRegistry.register(typeHandler);}publicTypeHandler<?>getTypeHandler(Class<?>javaType){returntypeHandlerRegistry.getTypeHandler(javaType);}}

外观模式分析:
外观角色Configuration

  • 封装了MyBatis的所有配置信息
  • 提供了统一的方法来访问各个组件
    子系统角色
  • XMLConfigBuilder:解析XML配置文件
  • MapperRegistry:管理Mapper接口
  • TypeHandlerRegistry:管理类型处理器
  • MappedStatement:管理SQL映射语句
    客户端SqlSessionFactoryBuilderDefaultSqlSessionFactory

设计优势

  1. 简化使用:用户只需要配置Configuration,不需要了解内部复杂的解析和初始化过程
  2. 解耦SqlSessionFactory只依赖Configuration外观类,不直接依赖各个子系统
  3. 可维护性:配置逻辑的变化被封装在Configuration和相关子系统中
  4. 灵活性:可以通过扩展Configuration来支持不同的配置方式
    这种多层外观设计使得MyBatis具有很好的层次结构和模块化,每个层次都封装了特定的复杂性,为上层提供简单的接口。

参考:

  • 韩顺平 Java设计模式
  • 张维鹏 Java设计模式之结构型:外观模式
  • kosamino 设计模式之外观模式(Facade)详解及代码示例
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 3:30:07

数字频率计入门指南:从信号输入到显示

从零构建数字频率计&#xff1a;信号、时基与计数的硬核实战你有没有遇到过这样的场景&#xff1f;手里的函数发生器输出一个波形&#xff0c;你想确认它的频率是不是真的10kHz&#xff0c;但万用表只能测电压&#xff0c;示波器又太复杂。这时候&#xff0c;如果有一个小巧精准…

作者头像 李华
网站建设 2026/4/18 3:28:20

Web 网站如何用 XinServer 做会员系统?

Web 网站如何用 XinServer 做会员系统&#xff1f; 最近有个做前端的朋友找我吐槽&#xff0c;说接了个外包小项目&#xff0c;要做一个带会员系统的官网。前端页面他刷刷刷两天就搞定了&#xff0c;结果卡在后端和数据库上。光是设计用户表、写注册登录接口、搞权限控制&#…

作者头像 李华
网站建设 2026/4/18 5:12:59

手把手教学:用Ollama一键运行HY-MT1.8B翻译模型

手把手教学&#xff1a;用Ollama一键运行HY-MT1.8B翻译模型 1. 引言&#xff1a;轻量级翻译模型的工程突破 在大模型参数竞赛愈演愈烈的背景下&#xff0c;腾讯混元团队于2025年12月开源了 HY-MT1.5-1.8B ——一款专为高效机器翻译设计的轻量级神经网络模型。该模型仅18亿参数…

作者头像 李华
网站建设 2026/4/18 5:12:54

AI人脸隐私卫士提升处理吞吐量:并发请求优化教程

AI人脸隐私卫士提升处理吞吐量&#xff1a;并发请求优化教程 1. 背景与挑战 随着AI技术在图像处理领域的广泛应用&#xff0c;个人隐私保护逐渐成为公众关注的核心议题。尤其在社交分享、公共监控、医疗影像等场景中&#xff0c;如何高效、安全地对人脸信息进行脱敏处理&…

作者头像 李华
网站建设 2026/4/17 16:02:18

人体关键点检测全攻略:从理论到实践,云端GPU省万元

人体关键点检测全攻略&#xff1a;从理论到实践&#xff0c;云端GPU省万元 引言&#xff1a;为什么你需要人体关键点检测技术&#xff1f; 想象一下&#xff0c;你正在设计一款智能健身镜&#xff0c;需要实时捕捉用户的运动姿态&#xff1b;或者开发一个虚拟试衣系统&#x…

作者头像 李华
网站建设 2026/4/18 5:13:55

远程医疗康复评估:隐私安全的骨骼点方案

远程医疗康复评估&#xff1a;隐私安全的骨骼点方案 引言 在互联网医疗快速发展的今天&#xff0c;居家康复治疗正成为越来越多患者的选择。但如何远程精准评估患者的康复动作&#xff0c;同时保护敏感的医疗隐私数据&#xff0c;成为开发人员面临的两大挑战。想象一下&#…

作者头像 李华