文章目录
- 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 适合的场景
- 需要简化复杂子系统接口时,为子系统提供一个统一的入口
- 客户端与多个子系统之间存在大量依赖关系,希望降低耦合度
- 需要将子系统分层,为每一层提供统一的接口
- 系统需要逐步重构,可以先引入外观模式,然后逐步迁移到新系统
2.2 常见场景举例
- 电脑启动过程:用户只需按下电源键,无需了解BIOS、CPU、内存等组件的复杂交互
- 数据库连接:JDBC驱动管理器封装了不同数据库的连接细节
- Web服务接口:REST API网关整合多个微服务的调用
- 日志框架:SLF4J作为Logback、Log4j等日志实现的外观
- 支付系统:支付网关整合支付宝、微信支付、银联等不同支付渠道
3. 实现方法
3.1 实现思路
- 识别复杂子系统:分析系统中的各个组件和它们之间的依赖关系
- 定义外观接口:确定需要为客户端提供的简化操作
- 实现外观类:创建外观类,封装子系统的复杂调用逻辑
- 客户端通过外观类访问:客户端只与外观类交互,不直接调用子系统
- 可选:抽象外观:如果需要支持多个子系统变体,可以引入抽象外观类
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映射语句
客户端 :SqlSessionFactoryBuilder、DefaultSqlSessionFactory等
设计优势:
- 简化使用 :用户只需要配置
Configuration,不需要了解内部复杂的解析和初始化过程 - 解耦 :
SqlSessionFactory只依赖Configuration外观类,不直接依赖各个子系统 - 可维护性 :配置逻辑的变化被封装在
Configuration和相关子系统中 - 灵活性 :可以通过扩展
Configuration来支持不同的配置方式
这种多层外观设计使得MyBatis具有很好的层次结构和模块化,每个层次都封装了特定的复杂性,为上层提供简单的接口。
参考: