外观模式(Facade Pattern)
一、模式概述
外观模式是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口。外观模式定义了一个更高层次的接口,使得子系统更加容易使用。
核心思想:隐藏系统的复杂性,只暴露一个简单的接口给客户端。就像餐厅的服务员,你只需要跟服务员点菜,而不需要自己去厨房告诉每个厨师怎么做。
二、角色说明
| 角色 | 说明 |
|---|---|
| Facade(外观角色) | 知道哪些子系统类负责处理请求,将客户端的请求代理给合适的子系统对象 |
| SubSystem(子系统角色) | 实现子系统功能,处理由 Facade 对象指派的任务,不知道 Facade 的存在 |
| Client(客户端) | 通过外观角色来调用子系统的功能 |
三、生活中的例子
- 电脑开机:按下电源按钮,外观模式帮你完成 CPU初始化、内存检测、硬盘加载、操作系统启动等一系列复杂操作
- 医院导诊台:你不需要分别去挂号、检查、化验,导诊台帮你安排好一切
- 智能家居:说一句"我要睡觉了",外观模式帮你关灯、拉窗帘、锁门、开空调
四、示例1:家庭影院外观
问题场景
家庭影院包含多个设备:投影仪、音响、DVD播放器、灯光。每次看电影都需要操作这些设备,非常繁琐。
代码实现
java
// ========== 子系统:各个独立设备 ==========
/**
* 投影仪
*/
class Projector {
public void on() {
System.out.println("投影仪开机...");
}
public void off() {
System.out.println("投影仪关机...");
}
public void wideScreenMode() {
System.out.println("投影仪切换到宽屏模式...");
}
}
/**
* 音响系统
*/
class Amplifier {
public void on() {
System.out.println("功放开机...");
}
public void off() {
System.out.println("功放关机...");
}
public void setVolume(int level) {
System.out.println("设置音量为: " + level);
}
public void setSurroundSound() {
System.out.println("开启环绕声...");
}
}
/**
* DVD播放器
*/
class DvdPlayer {
public void on() {
System.out.println("DVD播放器开机...");
}
public void off() {
System.out.println("DVD播放器关机...");
}
public void play(String movie) {
System.out.println("正在播放电影: " + movie);
}
public void stop() {
System.out.println("停止播放...");
}
}
/**
* 灯光系统
*/
class TheaterLights {
public void on() {
System.out.println("灯光打开...");
}
public void off() {
System.out.println("灯光关闭...");
}
public void dim(int level) {
System.out.println("灯光调暗到: " + level + "%");
}
}
/**
* 屏幕
*/
class Screen {
public void up() {
System.out.println("屏幕上卷...");
}
public void down() {
System.out.println("屏幕下降...");
}
}
// ========== 外观角色 ==========
/**
* 家庭影院外观
* 封装了所有设备的复杂操作,提供简单的接口
*/
class HomeTheaterFacade {
private Projector projector;
private Amplifier amplifier;
private DvdPlayer dvdPlayer;
private TheaterLights lights;
private Screen screen;
public HomeTheaterFacade() {
this.projector = new Projector();
this.amplifier = new Amplifier();
this.dvdPlayer = new DvdPlayer();
this.lights = new TheaterLights();
this.screen = new Screen();
}
/**
* 一键观影模式:所有设备自动就绪
*/
public void watchMovie(String movie) {
System.out.println("=== 准备观影: " + movie + " ===");
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amplifier.on();
amplifier.setSurroundSound();
amplifier.setVolume(8);
dvdPlayer.on();
dvdPlayer.play(movie);
System.out.println("=== 一切就绪,请享受电影! ===\n");
}
/**
* 一键关闭:所有设备自动关闭
*/
public void endMovie() {
System.out.println("=== 关闭家庭影院 ===");
dvdPlayer.stop();
dvdPlayer.off();
amplifier.off();
projector.off();
screen.up();
lights.on();
System.out.println("=== 家庭影院已关闭 ===\n");
}
/**
* 暂停模式
*/
public void pauseMovie() {
System.out.println("=== 暂停播放 ===");
lights.dim(50);
dvdPlayer.stop();
System.out.println("=== 已暂停 ===\n");
}
}
// ========== 客户端代码 ==========
public class HomeTheaterDemo {
public static void main(String[] args) {
HomeTheaterFacade theater = new HomeTheaterFacade();
// 以前需要这样操作(复杂)
// theater.lights.dim(10);
// theater.screen.down();
// theater.projector.on();
// ... 一堆操作
// 现在只需要一行代码
theater.watchMovie("流浪地球");
theater.pauseMovie();
theater.endMovie();
}
}运行结果
=== 准备观影: 流浪地球 ===
灯光调暗到: 10%
屏幕下降...
投影仪开机...
投影仪切换到宽屏模式...
功放开机...
开启环绕声...
设置音量为: 8
DVD播放器开机...
正在播放电影: 流浪地球
=== 一切就绪,请享受电影! ===
=== 暂停播放 ===
灯光调暗到: 50%
停止播放...
=== 已暂停 ===
=== 关闭家庭影院 ===
停止播放...
DVD播放器关机...
功放关机...
投影仪关机...
屏幕上卷...
灯光打开...
=== 家庭影院已关闭 ===五、示例2:订单处理外观
问题场景
一个电商订单处理涉及库存检查、价格计算、支付处理、物流安排、通知发送等多个子系统。
java
// ========== 子系统 ==========
/**
* 库存子系统
*/
class InventoryService {
public boolean checkStock(String productId, int quantity) {
System.out.println("[库存] 检查商品 " + productId + " 库存,数量: " + quantity);
return true; // 假设库存充足
}
public void deductStock(String productId, int quantity) {
System.out.println("[库存] 扣减商品 " + productId + " 库存 " + quantity + " 件");
}
}
/**
* 价格子系统
*/
class PricingService {
public double calculatePrice(String productId, int quantity) {
double price = 99.0 * quantity;
System.out.println("[价格] 计算价格: " + quantity + " x 99.0 = " + price);
return price;
}
public double applyDiscount(double price, String couponCode) {
double discounted = price * 0.9;
System.out.println("[价格] 使用优惠券 " + couponCode + ",折后价: " + discounted);
return discounted;
}
}
/**
* 支付子系统
*/
class PaymentService {
public boolean processPayment(double amount, String method) {
System.out.println("[支付] 使用 " + method + " 支付 " + amount + " 元");
return true;
}
}
/**
* 物流子系统
*/
class ShippingService {
public String arrangeShipping(String orderId, String address) {
String trackingNo = "SF" + System.currentTimeMillis();
System.out.println("[物流] 订单 " + orderId + " 已安排发货至 " + address);
System.out.println("[物流] 快递单号: " + trackingNo);
return trackingNo;
}
}
/**
* 通知子系统
*/
class NotificationService {
public void sendOrderConfirmation(String orderId, String email) {
System.out.println("[通知] 订单确认邮件已发送至 " + email);
}
public void sendShippingNotification(String orderId, String trackingNo) {
System.out.println("[通知] 发货通知已发送,快递单号: " + trackingNo);
}
}
// ========== 外观角色 ==========
/**
* 订单处理外观
* 将多个子系统的复杂流程封装为简单的一键操作
*/
class OrderFacade {
private InventoryService inventory;
private PricingService pricing;
private PaymentService payment;
private ShippingService shipping;
private NotificationService notification;
public OrderFacade() {
this.inventory = new InventoryService();
this.pricing = new PricingService();
this.payment = new PaymentService();
this.shipping = new ShippingService();
this.notification = new NotificationService();
}
/**
* 一键下单:检查库存 -> 计算价格 -> 支付 -> 发货 -> 通知
*/
public boolean placeOrder(String productId, int quantity,
String couponCode, double totalAmount,
String paymentMethod, String address,
String orderId, String email) {
System.out.println("========== 开始处理订单: " + orderId + " ==========\n");
// 1. 检查库存
if (!inventory.checkStock(productId, quantity)) {
System.out.println("库存不足,订单处理失败!");
return false;
}
// 2. 计算价格
double price = pricing.calculatePrice(productId, quantity);
if (couponCode != null) {
price = pricing.applyDiscount(price, couponCode);
}
// 3. 处理支付
if (!payment.processPayment(price, paymentMethod)) {
System.out.println("支付失败,订单处理失败!");
return false;
}
// 4. 扣减库存
inventory.deductStock(productId, quantity);
// 5. 安排发货
String trackingNo = shipping.arrangeShipping(orderId, address);
// 6. 发送通知
notification.sendOrderConfirmation(orderId, email);
notification.sendShippingNotification(orderId, trackingNo);
System.out.println("\n========== 订单 " + orderId + " 处理完成 ==========\n");
return true;
}
/**
* 一键取消订单
*/
public void cancelOrder(String orderId, String productId, int quantity) {
System.out.println("========== 取消订单: " + orderId + " ==========");
System.out.println("[取消] 订单 " + orderId + " 已取消");
System.out.println("[库存] 恢复商品 " + productId + " 库存 " + quantity + " 件");
System.out.println("[支付] 退款处理中...");
System.out.println("[通知] 取消通知已发送");
System.out.println("========== 订单取消完成 ==========\n");
}
}
// ========== 客户端代码 ==========
public class OrderFacadeDemo {
public static void main(String[] args) {
OrderFacade orderFacade = new OrderFacade();
// 以前需要分别调用各个子系统
// inventory.checkStock(...)
// pricing.calculatePrice(...)
// payment.processPayment(...)
// shipping.arrangeShipping(...)
// notification.sendOrderConfirmation(...)
// ... 十几行代码
// 现在只需要一行
orderFacade.placeOrder(
"SKU001", 2, "VIP10",
198.0, "微信支付",
"北京市海淀区",
"ORD20250101001",
"user@example.com"
);
orderFacade.cancelOrder("ORD20250101001", "SKU001", 2);
}
}六、UML 类图
┌──────────────┐
│ Client │
└──────┬───────┘
│ 调用
▼
┌──────────────────┐
│ HomeTheaterFacade │ ←── 外观角色
│ + watchMovie() │
│ + endMovie() │
└──────┬───────────┘
│ 组合
▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Projector │ │Amplifier │ │DvdPlayer │ │ Lights │ ←── 子系统
│ +on() │ │ +on() │ │ +on() │ │ +on() │
│ +off() │ │ +off() │ │ +play() │ │ +dim() │
└──────────┘ └──────────┘ └──────────┘ └──────────┘七、优缺点分析
优点
- 简化接口:客户端代码变得非常简洁,不需要了解子系统的复杂细节
- 解耦:客户端与子系统之间没有直接耦合关系,通过外观角色间接交互
- 符合迪米特法则(最少知识原则):客户端只需要知道外观角色即可
- 分层架构:可以在子系统和客户端之间增加外观层,提高层次独立性
缺点
- 不够灵活:如果需要添加新的子系统方法,可能需要修改外观类
- 过度使用:如果系统本身并不复杂,强行使用外观模式反而增加了复杂度
- 违背开闭原则:新增子系统可能需要修改外观类的代码
八、使用场景
- 复杂子系统简化:当一个系统很复杂时,提供一个简单的接口
- 分层架构:在每层定义入口点,层与层之间通过外观交互
- 遗留系统集成:为遗留系统提供一个简单的接口,隔离旧系统的变化
- 模块化设计:为子系统提供统一的接口,方便其他模块使用
九、与其他模式的关系
- 适配器模式 vs 外观模式:适配器改变接口以匹配客户端期望,外观模式简化接口但不改变接口
- 中介者模式 vs 外观模式:中介者让子系统之间互相通信,外观模式只让客户端与子系统单向通信
- 单例模式:外观对象通常可以设计为单例
十、最佳实践
- 不要过度设计:如果一个方法调用就能解决问题,不需要外观模式
- 保持外观的稳定性:外观类的接口一旦确定,尽量不要频繁修改
- 可以有多个外观:同一个子系统可以有多个不同层次的外观
- 外观不是万能的:当客户端需要直接访问子系统时,应该绕过外观直接调用
总结:外观模式是简化复杂系统的利器,它通过提供统一的高层接口,让客户端可以轻松使用复杂的子系统。在实际开发中,我们经常会无意中使用到外观模式,比如封装数据库操作、统一API接口等。关键在于找到真正需要简化的复杂度,而不是为了使用模式而使用模式。