什么是外观模式?
外观模式(Facade Pattern)是一种结构型设计模式,它为复杂的子系统提供一个统一的接口,让客户端可以更简单地使用子系统。外观模式隐藏了系统的复杂性,提供了一个客户端可以轻松访问的接口。
生活比喻
想象你买电脑的过程。如果你自己去组装,需要分别联系CPU厂商、内存厂商、主板厂商、显卡厂商...这太复杂了!但如果你去电脑城找一家"装机店",他们帮你搞定一切。你只需要告诉他们你的需求和预算。装机店就是"外观",它封装了所有子系统的复杂性,对你隐藏了细节。
外观模式结构:
没有外观模式(客户端直接调用子系统):
┌──────────┐ ┌─────────┐
│ 客户端A │ ──► │ 子系统1 │ ← CPU
│ 客户端B │ ──► │ 子系统2 │ ← 主板
│ 客户端C │ ──► │ 子系统3 │ ← 内存
└──────────┘ │ 子系统4 │ ← 硬盘
│ ... │
└─────────┘
有外观模式(客户端通过外观调用子系统):
┌──────────┐
│ 客户端 │ ┌─────────────┐ ┌─────────┐
│ │ ───► │ Facade │ ───► │ 子系统1 │
│ │ │ (外观类) │ ───► │ 子系统2 │
│ │ │ │ ───► │ 子系统3 │
└──────────┘ └─────────────┘ └─────────┘
为什么需要外观模式?
没有外观模式的问题
// 客户端需要知道所有子系统的细节
public class WithoutFacade {
public static void main(String[] args) {
// 启动电脑,需要调用很多子系统
CPU cpu = new CPU();
Memory memory = new Memory();
HardDisk disk = new HardDisk();
GraphicsCard gpu = new GraphicsCard();
Monitor monitor = new Monitor();
// 客户端需要知道每个组件的启动顺序和细节
cpu.start();
memory.load();
disk.read();
gpu.initialize();
monitor.turnOn();
// 太复杂了!客户端不应该知道这些细节
}
}
完整实现
1. 定义子系统类
// 子系统A - CPU
class CPU {
public void start() {
System.out.println(" [CPU] 启动...");
System.out.println(" [CPU] 执行指令...");
}
public void shutdown() {
System.out.println(" [CPU] 关闭...");
}
}
// 子系统B - 内存
class Memory {
public void load() {
System.out.println(" [Memory] 加载数据到内存...");
}
public void refresh() {
System.out.println(" [Memory] 刷新内存...");
}
}
// 子系统C - 硬盘
class HardDisk {
public void read() {
System.out.println(" [HardDisk] 从硬盘读取数据...");
}
public void write() {
System.out.println(" [HardDisk] 写入数据到硬盘...");
}
}
// 子系统D - 显卡
class GraphicsCard {
public void initialize() {
System.out.println(" [GPU] 初始化显卡驱动...");
}
public void display() {
System.out.println(" [GPU] 显示画面...");
}
}
// 子系统E - 显示器
class Monitor {
public void turnOn() {
System.out.println(" [Monitor] 显示器开机...");
}
public void turnOff() {
System.out.println(" [Monitor] 显示器关机...");
}
}
// 子系统F - 网络
class NetworkCard {
public void connect() {
System.out.println(" [Network] 连接网络...");
}
public void disconnect() {
System.out.println(" [Network] 断开网络连接...");
}
}
2. 创建外观类
// 外观类 - 提供统一的简化接口
class ComputerFacade {
// 持有所有子系统
private CPU cpu;
private Memory memory;
private HardDisk hardDisk;
private GraphicsCard graphicsCard;
private Monitor monitor;
private NetworkCard networkCard;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDisk = new HardDisk();
this.graphicsCard = new GraphicsCard();
this.monitor = new Monitor();
this.networkCard = new NetworkCard();
}
// 统一的开机方法 - 封装了所有细节
public void startComputer() {
System.out.println("====== 开机流程开始 ======");
cpu.start();
memory.load();
hardDisk.read();
graphicsCard.initialize();
monitor.turnOn();
networkCard.connect();
System.out.println("====== 开机完成! ======\n");
}
// 统一的关机方法
public void shutdownComputer() {
System.out.println("====== 关机流程开始 ======");
networkCard.disconnect();
monitor.turnOff();
memory.refresh();
hardDisk.write();
cpu.shutdown();
System.out.println("====== 关机完成 ======\n");
}
// 玩游戏模式 - 针对特定场景的便捷方法
public void playGame() {
System.out.println("====== 启动游戏模式 ======");
cpu.start();
memory.load();
graphicsCard.initialize();
monitor.turnOn();
networkCard.connect();
System.out.println("====== 游戏已就绪! ======\n");
}
}
3. 客户端使用
public class FacadeDemo {
public static void main(String[] args) {
// 创建外观
ComputerFacade computer = new ComputerFacade();
System.out.println("===== 场景1:日常办公 =====");
computer.startComputer();
System.out.println("===== 场景2:玩游戏 =====");
computer.playGame();
System.out.println("===== 场景3:关机 =====");
computer.shutdownComputer();
}
}
实战案例:用户注册系统
在用户注册系统中,外观模式可以封装邮箱服务、短信服务、积分服务等:
// 子系统:邮件服务
class EmailService {
public void sendWelcomeEmail(String email) {
System.out.println("[Email] 发送欢迎邮件到: " + email);
}
}
// 子系统:短信服务
class SMSService {
public void sendVerifyCode(String phone) {
System.out.println("[SMS] 发送验证码到: " + phone);
}
}
// 子系统:积分服务
class PointService {
public void addPoints(String userId, int points) {
System.out.println("[Points] 给用户 " + userId + " 增加 " + points + " 积分");
}
}
// 子系统:日志服务
class LogService {
public void log(String message) {
System.out.println("[Log] " + message);
}
}
// 外观:用户注册服务
class UserRegisterFacade {
private EmailService emailService;
private SMSService smsService;
private PointService pointService;
private LogService logService;
public UserRegisterFacade() {
this.emailService = new EmailService();
this.smsService = new SMSService();
this.pointService = new PointService();
this.logService = new LogService();
}
// 统一的注册方法
public void register(String username, String email, String phone) {
System.out.println("\n==== 用户注册流程 ====");
// 1. 保存用户到数据库(假设)
logService.log("保存用户: " + username);
String userId = "U" + System.currentTimeMillis();
// 2. 发送欢迎邮件
emailService.sendWelcomeEmail(email);
// 3. 发送验证码
smsService.sendVerifyCode(phone);
// 4. 赠送注册积分
pointService.addPoints(userId, 100);
logService.log("用户注册成功: " + userId);
System.out.println("==== 注册完成 ====\n");
}
}
// 使用
public class UserRegisterDemo {
public static void main(String[] args) {
UserRegisterFacade registerService = new UserRegisterFacade();
registerService.register("张三", "zhang@example.com", "13800138000");
}
}
外观模式的优缺点
优点
简化接口:客户端不需要了解子系统细节
解耦:客户端与子系统解耦
易用:降低学习成本
缺点
过度封装:可能隐藏了客户端需要的功能
违反开闭:添加新子系统可能需要修改外观类
外观模式 vs 其他模式
| 模式 | 目的 | 关注点 |
|---|---|---|
| 外观模式 | 简化接口 | 封装子系统,提供统一入口 |
| 适配器模式 | 接口转换 | 将不兼容接口转换为可兼容 |
| 装饰器模式 | 动态增强 | 为对象添加新功能 |
| 观察者模式 | 通知机制 | 一对多依赖,状态变化自动通知 |
应用场景
1
复杂类库/框架封装
MyBatis的SqlSession封装了JDBC细节
2
企业应用集成
ESB(企业服务总线)作为外观
3
第三方SDK封装
封装微信支付、支付宝等复杂API
小结
- 外观模式为复杂子系统提供统一接口,简化客户端调用
- 外观类持有所有子系统实例,封装调用顺序和逻辑
- 客户端通过外观访问子系统,无需了解内部细节
- 符合迪米特法则(最少知道原则)
- 常用于封装复杂类库、框架、第三方SDK