外观模式(Facade Pattern)是一种结构型设计模式,它为复杂子系统提供一个统一的简化接口,客户端通过这个接口与子系统交互,而无需直接操作子系统中的多个组件。

简单来说,外观模式就像 “前台服务员”—— 当你去餐厅吃饭时,不需要直接和厨房、吧台、保洁等多个部门打交道,只需告诉前台服务员你的需求,由他协调协调内部各部门完成工作。

其UML类图如下:

image.png

从类图中我们不难看出,外观模式中:

  • 一个Facade外观:封装了ABC三个子系统,它负责与客户端直接通信
  • 子系统ABC:是实现了具体功能的系统,他们之间彼此没有依赖

外观模式的核心价值是降低复杂度解耦,通过引入外观类隔离客户端与子系统,让客户端更易用,同时保护子系统内部细节不被暴露。常见应用场景包括:框架的 API 封装(如 Spring 的JdbcTemplate简化 JDBC 操作)、复杂工具类的统一接口(如日志门面 SLF4J)等。

我们通过一个例子来说明。假设我们有一个电商系统,其中的订单支付后的处理场景包括了“库存扣减、订单状态更新、物流创建和短信通知”。他们分别由不同的子系统完成。那么这个流程可以通过外观模式实现,把其他几个子系统的处理封装到一个Facade中,从而降低了对其他子系统的耦合,也提高了安全性。

首先,假设我们有以下几个子系统的代码:

  1. 库存子系统,负责扣减库存
1
2
3
4
5
6
7
8
class InventorySystem {
// 扣减库存(模拟复杂逻辑:校验库存、锁定库存、更新数据库)
public boolean reduceStock(String productId, int quantity) {
System.out.println("[库存系统] 校验商品[" + productId + "]库存...");
System.out.println("[库存系统] 扣减库存:商品ID=" + productId + ",数量=" + quantity);
return true; // 模拟扣减成功
}
}
  1. 订单子系统中的方法完成了订单状态的更改
1
2
3
4
5
6
7
class OrderSystem {
// 更新订单为“已支付”状态(模拟复杂逻辑:状态流转校验、记录操作日志)
public void updateOrderStatus(String orderId, String status) {
System.out.println("[订单系统] 校验订单[" + orderId + "]当前状态...");
System.out.println("[订单系统] 订单状态更新为:" + status + "(订单ID=" + orderId + ")");
}
}
  1. 物流子系统中的方法负责创建物流单
1
2
3
4
5
6
7
8
9
class LogisticsSystem {
// 创建物流单(模拟复杂逻辑:匹配快递公司、生成物流单号、推送物流信息)
public String createLogisticsOrder(String orderId, String recipientAddress) {
System.out.println("[物流系统] 为订单[" + orderId + "]匹配快递公司...");
String logisticsNo = "SF1234567890"; // 模拟生成的物流单号
System.out.println("[物流系统] 生成物流单:单号=" + logisticsNo + ",收货地址=" + recipientAddress);
return logisticsNo;
}
}
  1. 通知子系统负责发出短信通知客户
1
2
3
4
5
6
7
class NotificationSystem {
// 发送支付成功短信(模拟复杂逻辑:模板渲染、调用短信网关、记录发送日志)
public void sendPaymentSuccessSms(String phone, String orderId, String logisticsNo) {
String message = "【电商平台】您的订单(" + orderId + ")已支付成功,物流单号:" + logisticsNo + ",请注意查收!";
System.out.println("[通知系统] 向手机号[" + phone + "]发送短信:" + message);
}
}

现在我们来创建一个Facade类,来封装上述四个子系统的方法,让它在订单支付后完成一系列操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class PaymentPostProcessFacade {
// 持有各子系统的引用(子系统可通过依赖注入实现,此处简化为直接初始化)
private final InventorySystem inventorySystem;
private final OrderSystem orderSystem;
private final LogisticsSystem logisticsSystem;
private final NotificationSystem notificationSystem;

// 初始化所有子系统(实际项目中可通过Spring等框架自动注入)
public PaymentPostProcessFacade() {
this.inventorySystem = new InventorySystem();
this.orderSystem = new OrderSystem();
this.logisticsSystem = new LogisticsSystem();
this.notificationSystem = new NotificationSystem();
}

/**
* 对外提供的简化接口:支付成功后统一处理流程
* 客户端只需调用这一个方法,无需关心内部子系统如何交互
*/
public void processAfterPayment(String orderId, String productId, int quantity,
String recipientAddress, String userPhone) {
try {
// 1. 扣减库存(子系统1)
boolean stockReduced = inventorySystem.reduceStock(productId, quantity);
if (!stockReduced) {
throw new RuntimeException("库存不足,支付后处理失败");
}

// 2. 更新订单状态为“已支付”(子系统2)
orderSystem.updateOrderStatus(orderId, "PAID");

// 3. 创建物流单(子系统3)
String logisticsNo = logisticsSystem.createLogisticsOrder(orderId, recipientAddress);

// 4. 发送支付成功短信(子系统4)
notificationSystem.sendPaymentSuccessSms(userPhone, orderId, logisticsNo);

System.out.println("\n[支付后处理完成] 订单[" + orderId + "]所有后续操作执行成功!");
} catch (Exception e) {
System.err.println("[支付后处理失败] 原因:" + e.getMessage());
// 实际项目中会触发异常处理(如回滚库存、标记订单异常)
}
}
}

接下来,在客户端调用时,只需调用Facade类中的一个方法即可完成上述步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class EcommerceClient {
public static void main(String[] args) {
// 1. 模拟支付成功后接收到的参数
String orderId = "ORDER_20240925_001";
String productId = "PROD_10086";
int quantity = 2;
String recipientAddress = "北京市朝阳区XX街道";
String userPhone = "13800138000";

// 2. 调用外观类接口(仅需一行代码,无需操作4个子系统)
PaymentPostProcessFacade facade = new PaymentPostProcessFacade();
facade.processAfterPayment(orderId, productId, quantity, recipientAddress, userPhone);
}
}

从上面的例子中我们可以看出Facade模式的优势:

  1. 简化调用:客户端只需调用processAfterPayment一个方法,无需逐一调用 4 个子系统的接口,降低了代码复杂度。
  2. 隔离变化:若子系统逻辑修改(如库存系统增加 “库存预警” 步骤),只需修改外观类,客户端代码无需变动,符合 “开闭原则”。
  3. 统一流程:外观类封装了子系统的调用顺序(如必须先扣库存,再更新订单状态),避免客户端因调用顺序错误导致业务异常。
  4. 降低耦合:客户端与子系统完全解耦,未来替换子系统(如将短信通知换成 APP 推送),客户端无需感知。