文章目录
- 一、概述
-
- [1.1 结构与角色](#1.1 结构与角色)
- [1.2 适用场景](#1.2 适用场景)
- 二、实现方式
-
- [2.1 基础实现------智能家居系统](#2.1 基础实现——智能家居系统)
- [2.2 使用外观前后的对比](#2.2 使用外观前后的对比)
- [2.3 实际应用示例------应用启动引导系统](#2.3 实际应用示例——应用启动引导系统)
- [三、外观模式 vs 中介者模式](#三、外观模式 vs 中介者模式)
-
- [3.1 意图对比](#3.1 意图对比)
- [3.2 结构对比](#3.2 结构对比)
- [3.3 代码对比](#3.3 代码对比)
- [3.4 选择指南](#3.4 选择指南)
- 四、总结
一、概述
在软件开发中,经常会遇到这样的场景:一个复杂的子系统由多个组件组成,客户端需要依次调用这些组件的方法才能完成一个业务操作。客户端直接与子系统中的众多组件交互,不仅代码复杂、耦合度高,而且难以维护。例如,启动一台家庭影院需要先打开电视、再打开音响、再打开投影仪、再调暗灯光......客户端需要了解每个组件的细节。
外观模式(Facade Pattern)正是为了解决这个问题而诞生的------它为子系统中的一组接口提供一个一致的界面 ,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式不是新增功能,而是对已有功能的封装和简化。
生活中的外观模式例子比比皆是:
- 智能家居一键启动:按下一个"回家模式"按钮,系统自动开灯、开空调、拉窗帘、播放音乐,无需逐一操作每个设备
- 餐厅点餐:顾客只需告诉服务员"来一份套餐A",服务员会在后厨协调各岗位(凉菜、热菜、甜点、饮料)完成出餐,顾客不需要与后厨直接沟通
- 客服热线:拨打一个号码就能获得咨询、投诉、售后等多种服务,不需要记住各个部门的分机号
核心:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,使得子系统更加容易使用
1.1 结构与角色
外观模式包含以下角色:
Client 客户端
Facade 外观角色
SubsystemA 子系统A
SubsystemB 子系统B
SubsystemC 子系统C
组件A1
组件A2
组件B1
组件C1
- Facade(外观角色):为子系统提供统一的对外接口,将客户端的请求委派给适当的子系统对象处理
- Subsystem(子系统角色):子系统中的一组类,负责实际的功能实现,外观角色了解子系统角色的功能和职责,但子系统角色不了解外观角色的存在
- Client(客户端):通过外观角色与子系统交互,无需直接与子系统中的各个组件通信
1.2 适用场景
- 为复杂的子系统提供一个简单的对外接口,降低客户端与子系统之间的耦合度
- 客户端不需要了解子系统内部的复杂逻辑,只需要通过外观角色完成操作
- 需要构建多层结构的子系统,每层之间通过外观角色进行通信
- 需要将子系统与客户端分离,使子系统的变化不会影响客户端
二、实现方式
外观模式的核心实现思路是:定义一个外观类,持有子系统中各组件的引用,并提供简化的方法将客户端的请求委派给子系统中适当的组件处理。
2.1 基础实现------智能家居系统
以智能家居系统为例,一个"回家模式"需要协调灯光、空调、窗帘、音乐等多个设备,外观角色将这一系列操作封装为一个简单的方法:
客户端
SmartHomeFacade 智能家居外观
Light 灯光
AirConditioner 空调
Curtain 窗帘
MusicPlayer 音乐播放器
(1)子系统------灯光
java
/**
* 子系统角色:灯光
*/
public class Light {
/**
* 开灯
*/
public void on() {
System.out.println("灯光:打开灯光");
}
/**
* 关灯
*/
public void off() {
System.out.println("灯光:关闭灯光");
}
/**
* 调节亮度
*
* @param level 亮度等级(0-100)
*/
public void setBrightness(int level) {
System.out.println("灯光:调节亮度为 " + level + "%");
}
}
(2)子系统------空调
java
/**
* 子系统角色:空调
*/
public class AirConditioner {
/**
* 开空调
*/
public void on() {
System.out.println("空调:打开空调");
}
/**
* 关空调
*/
public void off() {
System.out.println("空调:关闭空调");
}
/**
* 设置温度
*
* @param temperature 目标温度
*/
public void setTemperature(int temperature) {
System.out.println("空调:设置温度为 " + temperature + "℃");
}
}
(3)子系统------窗帘
java
/**
* 子系统角色:窗帘
*/
public class Curtain {
/**
* 打开窗帘
*/
public void open() {
System.out.println("窗帘:打开窗帘");
}
/**
* 关闭窗帘
*/
public void close() {
System.out.println("窗帘:关闭窗帘");
}
}
(4)子系统------音乐播放器
java
/**
* 子系统角色:音乐播放器
*/
public class MusicPlayer {
/**
* 播放音乐
*
* @param song 歌曲名
*/
public void play(String song) {
System.out.println("音乐:播放「" + song + "」");
}
/**
* 停止播放
*/
public void stop() {
System.out.println("音乐:停止播放");
}
}
(5)外观角色------智能家居外观
java
/**
* 外观角色:智能家居外观
* 封装子系统的复杂操作,提供简化的接口
*/
public class SmartHomeFacade {
private final Light light;
private final AirConditioner airConditioner;
private final Curtain curtain;
private final MusicPlayer musicPlayer;
public SmartHomeFacade() {
this.light = new Light();
this.airConditioner = new AirConditioner();
this.curtain = new Curtain();
this.musicPlayer = new MusicPlayer();
}
/**
* 回家模式------一键启动
*/
public void goHome() {
System.out.println("====== 回家模式启动 ======");
light.on();
light.setBrightness(70);
airConditioner.on();
airConditioner.setTemperature(26);
curtain.close();
musicPlayer.play("轻松音乐");
System.out.println("====== 回家模式就绪 ======");
}
/**
* 离家模式------一键关闭
*/
public void leaveHome() {
System.out.println("====== 离家模式启动 ======");
light.off();
airConditioner.off();
curtain.open();
musicPlayer.stop();
System.out.println("====== 离家模式完成 ======");
}
}
(6)客户端调用
java
public class SmartHomeDemo {
public static void main(String[] args) {
SmartHomeFacade smartHome = new SmartHomeFacade();
// 回家模式------一键启动,无需关心子系统细节
smartHome.goHome();
// ====== 回家模式启动 ======
// 灯光:打开灯光
// 灯光:调节亮度为 70%
// 空调:打开空调
// 空调:设置温度为 26℃
// 窗帘:关闭窗帘
// 音乐:播放「轻松音乐」
// ====== 回家模式就绪 ======
// 离家模式------一键关闭
smartHome.leaveHome();
// ====== 离家模式启动 ======
// 灯光:关闭灯光
// 空调:关闭空调
// 窗帘:打开窗帘
// 音乐:停止播放
// ====== 离家模式完成 ======
}
}
关键点 :客户端只需要调用
goHome()或leaveHome()即可完成复杂的操作,无需了解灯光、空调、窗帘、音乐播放器的具体接口和调用顺序。外观角色封装了子系统的复杂性,提供了简洁的对外接口。
2.2 使用外观前后的对比
如果不使用外观模式,客户端需要直接与子系统中每个组件交互:
java
/**
* 不使用外观模式------客户端直接操作子系统
*/
public class WithoutFacadeDemo {
public static void main(String[] args) {
// 客户端需要了解每个子系统的接口和调用顺序
Light light = new Light();
AirConditioner airConditioner = new AirConditioner();
Curtain curtain = new Curtain();
MusicPlayer musicPlayer = new MusicPlayer();
// 回家------需要逐一操作每个设备
light.on();
light.setBrightness(70);
airConditioner.on();
airConditioner.setTemperature(26);
curtain.close();
musicPlayer.play("轻松音乐");
// 如果需要修改回家模式的逻辑,需要在所有客户端代码中修改
}
}
| 对比维度 | 不使用外观模式 | 使用外观模式 |
|---|---|---|
| 客户端代码 | 冗长,需要了解所有子系统 | 简洁,只需调用外观方法 |
| 耦合度 | 高,客户端与子系统紧耦合 | 低,客户端只与外观耦合 |
| 可维护性 | 差,逻辑变更需修改所有客户端 | 好,只需修改外观类 |
| 可读性 | 低,操作步骤分散 | 高,语义化的方法名 |
2.3 实际应用示例------应用启动引导系统
以应用启动引导系统为例,一个应用的启动需要初始化配置、数据库连接、缓存、日志等子系统,外观角色将这一系列初始化操作封装为简单的方法:
客户端
AppStartupFacade 应用启动外观
ConfigManager 配置管理器
DatabaseConnector 数据库连接器
CacheManager 缓存管理器
LogManager 日志管理器
(1)子系统------配置管理器
java
/**
* 子系统角色:配置管理器
*/
public class ConfigManager {
/**
* 加载配置
*/
public void loadConfig() {
System.out.println("配置:加载应用配置文件");
}
/**
* 验证配置
*/
public void validateConfig() {
System.out.println("配置:验证配置项完整性");
}
}
(2)子系统------数据库连接器
java
/**
* 子系统角色:数据库连接器
*/
public class DatabaseConnector {
/**
* 初始化连接池
*/
public void initConnectionPool() {
System.out.println("数据库:初始化连接池");
}
/**
* 测试连接
*/
public void testConnection() {
System.out.println("数据库:测试连接是否正常");
}
}
(3)子系统------缓存管理器
java
/**
* 子系统角色:缓存管理器
*/
public class CacheManager {
/**
* 初始化缓存
*/
public void initCache() {
System.out.println("缓存:初始化本地缓存");
}
/**
* 预热缓存
*/
public void warmUp() {
System.out.println("缓存:预热热点数据");
}
}
(4)子系统------日志管理器
java
/**
* 子系统角色:日志管理器
*/
public class LogManager {
/**
* 初始化日志
*/
public void initLogger() {
System.out.println("日志:初始化日志框架");
}
/**
* 设置日志级别
*
* @param level 日志级别
*/
public void setLogLevel(String level) {
System.out.println("日志:设置日志级别为 " + level);
}
}
(5)外观角色------应用启动外观
java
/**
* 外观角色:应用启动外观
* 封装应用启动的复杂流程,提供简化的接口
*/
public class AppStartupFacade {
private final LogManager logManager;
private final ConfigManager configManager;
private final DatabaseConnector databaseConnector;
private final CacheManager cacheManager;
public AppStartupFacade() {
this.logManager = new LogManager();
this.configManager = new ConfigManager();
this.databaseConnector = new DatabaseConnector();
this.cacheManager = new CacheManager();
}
/**
* 启动应用------按顺序初始化所有子系统
*/
public void startup() {
System.out.println("====== 应用启动开始 ======");
// 1. 初始化日志(最先,后续流程需要记录日志)
logManager.initLogger();
logManager.setLogLevel("INFO");
// 2. 加载并验证配置
configManager.loadConfig();
configManager.validateConfig();
// 3. 初始化数据库连接
databaseConnector.initConnectionPool();
databaseConnector.testConnection();
// 4. 初始化缓存
cacheManager.initCache();
cacheManager.warmUp();
System.out.println("====== 应用启动完成 ======");
}
/**
* 关闭应用------按逆序释放资源
*/
public void shutdown() {
System.out.println("====== 应用关闭开始 ======");
cacheManager.initCache();
databaseConnector.initConnectionPool();
configManager.loadConfig();
logManager.initLogger();
System.out.println("====== 应用关闭完成 ======");
}
}
(6)客户端调用
java
public class AppStartupDemo {
public static void main(String[] args) {
AppStartupFacade app = new AppStartupFacade();
// 一键启动------无需关心子系统初始化的顺序和细节
app.startup();
// ====== 应用启动开始 ======
// 日志:初始化日志框架
// 日志:设置日志级别为 INFO
// 配置:加载应用配置文件
// 配置:验证配置项完整性
// 数据库:初始化连接池
// 数据库:测试连接是否正常
// 缓存:初始化本地缓存
// 缓存:预热热点数据
// ====== 应用启动完成 ======
// 一键关闭
app.shutdown();
}
}
优势体现:启动流程的顺序至关重要(日志→配置→数据库→缓存),外观角色封装了这一顺序逻辑。如果未来需要调整启动顺序或新增子系统,只需修改外观类,客户端代码无需任何改动。
三、外观模式 vs 中介者模式
外观模式和中介者模式都属于结构型模式,而且都涉及"封装多个对象之间的交互",但它们的意图 和交互方式截然不同。
3.1 意图对比
| 对比维度 | 外观模式 | 中介者模式 |
|---|---|---|
| 核心意图 | 简化子系统的接口,提供统一的对外入口 | 将对象之间的交互集中到一个中介者对象中,避免对象之间相互引用 |
| 解决的问题 | 客户端与子系统之间的耦合 | 对象之间复杂的网状引用关系 |
| 交互方式 | 单向:客户端 → 外观 → 子系统(子系统不知道外观的存在) | 双向:同事对象 ↔ 中介者 ↔ 同事对象 |
| 对象感知 | 子系统不知道外观的存在 | 同事对象知道中介者的存在 |
| 新增行为 | 外观可以提供新的接口组合 | 中介者可以定义新的交互逻辑 |
| 结构关系 | 外观是子系统的入口,一层封装 | 中介者是同事对象的枢纽,居中协调 |
3.2 结构对比
中介者模式
ColleagueA
Mediator
ColleagueB
外观模式
无感知
Client
Facade
SubsystemA
SubsystemB
外观模式:客户端通过外观角色访问子系统,外观角色单向地委派请求给子系统,子系统不知道外观角色的存在,交互是单向的。
中介者模式:同事对象通过中介者与其他同事对象交互,中介者与同事对象之间是双向关系,同事对象知道中介者的存在,交互是双向的。
3.3 代码对比
以"智能家居"为例,对比两种模式的应用方式:
外观模式的用法------客户端通过外观一键操作设备:
java
// 外观角色:封装设备操作,提供简化接口
public class SmartHomeFacade {
private final Light light = new Light();
private final AirConditioner ac = new AirConditioner();
/**
* 回家模式
*/
public void goHome() {
light.on(); // 外观单向调用子系统
ac.on();
ac.setTemperature(26);
}
}
// 客户端:只需调用外观
SmartHomeFacade facade = new SmartHomeFacade();
facade.goHome();
中介者模式的用法------设备之间通过中介者协调交互:
java
// 中介者接口
public interface SmartHomeMediator {
void notify(Device device, String event);
}
// 具体中介者:协调设备之间的联动
public class SmartHomeMediatorImpl implements SmartHomeMediator {
private Light light;
private AirConditioner ac;
@Override
public void notify(Device device, String event) {
if ("温度过高".equals(event)) {
// 空调报告温度过高 → 中介者联动开灯、降温
ac.on();
ac.setTemperature(24);
light.on();
light.setBrightness(50);
} else if ("离开房间".equals(event)) {
// 传感器报告离开 → 中介者联动关灯、关空调
light.off();
ac.off();
}
}
}
// 同事对象:知道中介者的存在
public class AirConditioner extends Device {
public AirConditioner(SmartHomeMediator mediator) {
super(mediator);
}
/**
* 检测温度变化
*/
public void checkTemperature(int currentTemp) {
if (currentTemp > 30) {
mediator.notify(this, "温度过高");
}
}
}
关键区别 :外观模式是单向调用 ------外观调用子系统,子系统不知道外观的存在;中介者模式是双向交互------同事对象与中介者互相感知,中介者根据一个同事对象的状态变化协调其他同事对象的响应。
3.4 选择指南
| 场景 | 选择 |
|---|---|
| 需要为复杂子系统提供简化的对外接口 | 外观模式 |
| 需要解耦客户端与子系统的直接依赖 | 外观模式 |
| 需要按固定流程编排多个子系统的操作 | 外观模式 |
| 对象之间存在复杂的网状引用关系 | 中介者模式 |
| 对象之间需要互相通信但不想直接引用 | 中介者模式 |
| 一对象的状态变化需要联动其他多个对象 | 中介者模式 |
四、总结
外观模式的核心思想是为子系统中的一组接口提供一个一致的界面,定义一个高层接口使子系统更加容易使用,从而降低客户端与子系统之间的耦合度。
优点:
- 简化客户端调用:客户端只需要与外观角色交互,无需了解子系统的复杂性
- 降低耦合度:客户端与子系统解耦,子系统的变化不会影响客户端
- 符合迪米特法则:客户端只需要知道外观角色的存在,不需要知道子系统的内部结构
- 分层设计:可以为子系统构建多层外观,每层提供不同粒度的接口
- 不影响子系统:子系统本身不感知外观的存在,可以独立使用
缺点:
- 可能成为上帝对象:如果外观类承担了过多职责,会变得臃肿,违背单一职责原则
- 不能替代子系统:外观模式只是简化接口,不能提供子系统没有的功能
- 可能引入不必要的简单性:对于需要灵活使用子系统高级功能的场景,外观可能反而成为一种限制
适用场景:
- 为复杂的子系统提供简单的对外接口
- 客户端与子系统之间存在强耦合,需要解耦
- 需要构建多层结构的子系统,每层之间通过外观角色通信
- 需要将遗留系统或第三方库的复杂接口封装为简单接口
外观 vs 中介者 :外观模式的意图是
简化接口,客户端通过外观单向调用子系统,子系统不知道外观的存在;中介者模式的意图是解耦对象间的交互,同事对象与中介者双向通信,中介者居中协调。两者虽然都涉及封装多个对象的交互,但交互方向和意图截然不同。
参考博客:
外观模式 | 菜鸟教程:https://www.runoob.com/design-pattern/facade-pattern.html