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

文章目录

  • 0.个人感悟
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析(MyBatis Configuration为例)](#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 内存 硬盘:

java 复制代码
// CPU子系统
public class CPU {  
    /**  
     * @description 冻结  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public void freeze() {  
        System.out.println("CPU冻结当前任务");  
    }  
  
    /**  
     * @param position 位置  
     * @description 跳转  
     * @author bigHao  
     * @date 2026/1/12  
     **/  
    public void jump(long position) {  
        System.out.println("CPU跳转到内存位置: " + position);  
    }  
  
    /**  
     * @description 执行  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public void execute() {  
        System.out.println("CPU开始执行指令");  
    }  
}

// 内存子系统
public class Memory {  
    /**  
     * @param position 位置  
     * @param data     字节数据  
     * @description // TODO  
     * @author bigHao  
     * @date 2026/1/12  
     **/  
    public void load(long position, byte[] data) {  
        System.out.println("内存加载数据到位置: " + position);  
    }  
  
    /**  
     * @description 释放  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public void shutdown() {  
        System.out.println("内存释放");  
    }  
}

// 硬盘子系统
public class HardDrive {  
    /**  
     * @param lba  扇区  
     * @param size 大小  
     * @return byte[]  
     * @description 读取数据  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public byte[] read(long lba, int size) {  
        System.out.println("硬盘读取扇区 " + lba + ",大小: " + size + " bytes");  
        return new byte[size];  
    }  
  
    /**  
     * @description 释放  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public void shutdown() {  
        System.out.println("硬盘读释放");  
    }  
}

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

java 复制代码
public class ComputerFacade {  
    // 启动内存地址常量  
    private static final long BOOT_ADDRESS = 0x7C00;  
    private static final long BOOT_SECTOR = 0;  
    private static final int SECTOR_SIZE = 512;  
  
  
    private CPU cpu;  
  
    private Memory memory;  
  
    private HardDrive hardDrive;  
  
    public ComputerFacade() {  
        cpu = new CPU();  
        memory = new Memory();  
        hardDrive = new HardDrive();  
    }  
  
    /**  
     * @description 启动  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public void start() {  
        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  
     **/    
     public void shutdown() {  
        System.out.println("=== 开始关闭计算机 ===\n");  
        // cpu停止  
        cpu.freeze();  
        // 内存停止  
        memory.shutdown();  
        // 硬盘停止  
        hardDrive.shutdown();  
    }  
  
    /**  
     * @description 重启  
     * @author bigHao  
     * @date 2026/1/12  
     **/    
     public void restart() {  
        System.out.println("=== 开始重启计算机 ===\n");  
  
        start();  
  
        shutdown();  
    }  
}

测试:

java 复制代码
public class Client {  
    static void main() {  
        // 只用与门面交互  
        ComputerFacade facade = new ComputerFacade();  
        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类结构

java 复制代码
// Configuration类充当了MyBatis的外观类
public class Configuration {
    // 存储各种配置信息
    protected Environment environment;
    protected TypeAliasRegistry typeAliasRegistry;
    protected TypeHandlerRegistry typeHandlerRegistry;
    protected MapperRegistry mapperRegistry;
    protected Map<String, MappedStatement> mappedStatements;
    protected Map<String, Cache> caches;
    
    // 各种配置方法 - 对外提供简单接口
    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
    }
    
    public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
    }
    
    public void addMappedStatement(MappedStatement ms) {
        mappedStatements.put(ms.getId(), ms);
    }
    
    public MappedStatement getMappedStatement(String id) {
        return mappedStatements.get(id);
    }
    
    // 类型处理器相关方法
    public void registerTypeHandler(TypeHandler<?> typeHandler) {
        typeHandlerRegistry.register(typeHandler);
    }
    
    public TypeHandler<?> getTypeHandler(Class<?> javaType) {
        return typeHandlerRegistry.getTypeHandler(javaType);
    }
}

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

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

设计优势

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

参考:

相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码2 天前
嵌入式学习路线
学习
毛小茛2 天前
计算机系统概论——校验码
学习
babe小鑫2 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms2 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下2 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。2 天前
2026.2.25监控学习
学习
im_AMBER2 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J2 天前
从“Hello World“ 开始 C++
c语言·c++·学习