设计模式学习(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 小时前
java学习--Vector
java·学习
_叶小格_2 小时前
ansible自动化入门基础
运维·笔记·学习·自动化·ansible
im_AMBER2 小时前
前端 + agent 开发学习路线
前端·学习·agent
名字不相符2 小时前
2026年1月13日NSSCTF之[WUSTCTF 2020]level2
学习·萌新
浩瀚地学2 小时前
【Java】集合-Collection
java·开发语言·经验分享·笔记·学习
chillxiaohan2 小时前
GO学习踩坑记录
开发语言·学习·golang
Geoking.3 小时前
【设计模式】享元模式(Flyweight)详解:用共享对象对抗内存爆炸
java·设计模式·享元模式
callJJ3 小时前
Spring设计模式与依赖注入详解
java·spring·设计模式·idea·工厂模式
sxlishaobin3 小时前
设计模式之组合模式
设计模式·组合模式