第13章:外观模式——给复杂系统一个“简化接口”
外观模式,说白了就是给一堆乱七八糟的子系统提供一个统一的“总开关”。你不需要知道内部怎么接线,按一下按钮,该亮的灯全亮,该转的风扇全转。我最早接触这个模式,是在做一个智能家居控制系统的项目。当时客户说:“我就想按一个‘离家模式’按钮,所有灯关掉、空调关闭、窗帘拉上、安防启动。”嗯,这不就是典型的外观模式吗?
13.1 什么是外观模式?
外观模式(Facade Pattern)属于结构型设计模式。它为一组复杂的子系统接口提供一个更高级的、统一的接口。这个高级接口让子系统更容易使用。
你想想看,我们平时用电脑,开机只需要按一下电源键。但背后发生了什么?电源自检、BIOS加载、操作系统启动、驱动加载、服务启动……这一堆流程,全被“开机键”这个外观给封装了。用户不需要知道这些,按就完了。
核心定义:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
13.2 外观模式的结构
外观模式的结构其实很简单,就三个角色:
- 外观(Facade): 对外提供统一接口,内部调用各个子系统的方法。
- 子系统(SubSystem): 一组完成特定功能的类,它们之间可能有复杂的依赖关系。
- 客户端(Client): 通过外观类来访问子系统,不直接与子系统交互。
下面这张图,是我自己画的外观模式结构图,你看一眼就明白了:
13.3 代码实现:一个订单系统的外观
我记得有一次帮一个电商平台重构订单提交流程。原来客户端下单要调用库存、支付、物流、通知四个子系统,代码耦合得一塌糊涂。后来我用外观模式重构了一下,清爽多了。
先看子系统代码:
// 子系统1:库存服务
public class InventoryService {
public boolean checkStock(String productId, int quantity) {
System.out.println("检查库存: " + productId + ", 数量: " + quantity);
return true; // 假设库存充足
}
public void reduceStock(String productId, int quantity) {
System.out.println("扣减库存: " + productId + ", 数量: " + quantity);
}
}
// 子系统2:支付服务
public class PaymentService {
public boolean processPayment(String orderId, double amount) {
System.out.println("处理支付: 订单 " + orderId + ", 金额 " + amount);
return true;
}
}
// 子系统3:物流服务
public class ShippingService {
public void createShipping(String orderId, String address) {
System.out.println("创建物流: 订单 " + orderId + ", 地址 " + address);
}
}
// 子系统4:通知服务
public class NotificationService {
public void sendOrderConfirmation(String email, String orderId) {
System.out.println("发送确认邮件: " + email + ", 订单 " + orderId);
}
}
然后是我们的外观类:
// 外观类:订单服务
public class OrderFacade {
private InventoryService inventory;
private PaymentService payment;
private ShippingService shipping;
private NotificationService notification;
public OrderFacade() {
this.inventory = new InventoryService();
this.payment = new PaymentService();
this.shipping = new ShippingService();
this.notification = new NotificationService();
}
public void placeOrder(String productId, int quantity,
double amount, String address, String email) {
System.out.println("===== 开始下单 =====");
// 1. 检查库存
if (!inventory.checkStock(productId, quantity)) {
System.out.println("库存不足,下单失败");
return;
}
// 2. 扣减库存
inventory.reduceStock(productId, quantity);
// 3. 处理支付
String orderId = "ORD" + System.currentTimeMillis();
if (!payment.processPayment(orderId, amount)) {
System.out.println("支付失败,下单失败");
// 这里应该回滚库存,简化处理
return;
}
// 4. 创建物流
shipping.createShipping(orderId, address);
// 5. 发送通知
notification.sendOrderConfirmation(email, orderId);
System.out.println("===== 下单成功 =====");
}
}
客户端调用变得极其简单:
public class Client {
public static void main(String[] args) {
OrderFacade orderFacade = new OrderFacade();
// 一行代码完成下单
orderFacade.placeOrder("P1001", 2, 299.99,
"北京市朝阳区", "user@example.com");
}
}
个人经验:我在实际项目中,经常把外观类设计成单例模式。因为外观类本身是无状态的,多个客户端共享一个实例完全没问题。这样还能节省内存,避免重复创建子系统的实例。
13.4 迪米特法则——外观模式的理论基础
外观模式背后有一个重要的设计原则——迪米特法则(Law of Demeter),也叫最少知识原则。它的核心思想是:一个对象应该对其他对象有最少的了解。
说白了就是:不要和陌生人说话。你只需要跟你直接的朋友交互,不要绕来绕去调用别人的内部方法。
违反迪米特法则的代码长这样:
// 糟糕的设计:客户端直接操作子系统内部
public void badOrderProcess() {
InventoryService inv = new InventoryService();
PaymentService pay = new PaymentService();
inv.checkStock("P1001", 2);
inv.reduceStock("P1001", 2);
pay.processPayment("ORD123", 299.99);
// ... 客户端知道太多内部细节了
}
遵守迪米特法则的设计:
// 好的设计:客户端只跟外观交互
public void goodOrderProcess() {
OrderFacade facade = new OrderFacade();
facade.placeOrder("P1001", 2, 299.99, "北京", "user@example.com");
}
我曾经踩过的坑:有一次我过度追求迪米特法则,把外观类设计得特别大,里面塞了几十个方法。结果外观类变成了“上帝类”,耦合反而更高了。记住,外观模式是简化接口,不是把所有逻辑都塞进一个类里。如果一个外观类超过20个方法,就该考虑拆分了。
13.5 外观模式的优缺点
| 优点 | 缺点 |
|---|---|
|
|
我的建议:外观模式不是银弹。如果你的子系统本身就很稳定,或者客户端需要灵活调用子系统的各种组合,那就不适合用外观。我一般会在以下情况使用:子系统接口变化频繁、客户端只需要固定几种操作流程、或者想给第三方提供简化API。
13.6 应用场景
外观模式在实际项目中非常常见,我列举几个典型的场景:
- 复杂系统的简化入口:比如一个视频转换工具,内部涉及解码、滤镜、编码、封装等多个步骤。外观类提供一个 convert() 方法,客户端传个文件路径就完事了。
- 遗留系统接口封装:老系统的接口往往又乱又难用。用外观模式包一层,新系统只跟外观交互。我做过一个银行核心系统的接口封装,老系统有200多个方法,外观只暴露了20个。
- 分层架构中的服务层:在DDD架构中,Application Service 本质上就是一个外观。它协调领域对象、仓储、基础设施来完成一个用例。
- 第三方库的适配:有些第三方库API设计得很糟糕,用外观模式包一层,让你的业务代码跟第三方库解耦。哪天换库了,只改外观类就行。
- 单元测试的替身:外观类可以方便地做Mock。你不需要Mock所有子系统,只需要Mock外观类就行。
嗯,外观模式就讲到这里。它不是什么高深的技术,但用好了能让你的代码结构清晰很多。记住一句话:给复杂系统一个简单的门面,让调用者只关心“做什么”,不关心“怎么做”。
公众号:蓝海资料掘金营,微信deep3321