【设计模式】# 外观模式(Facade)大白话讲解!
外观模式(Facade)大白话讲解
一句话概括
就像酒店前台:你不需要知道后厨、保洁、维修等部门怎么工作,只要找前台就能搞定一切

现实生活比喻
场景1:酒店前台
- 复杂子系统:客房服务、餐饮部、保洁部、维修部、结算部
- 外观:前台接待员
- 你:只需要告诉前台"我要入住",剩下的事前台帮你协调
场景2:汽车启动
- 复杂子系统:发动机、油泵、点火系统、电池、传动系统
- 外观:钥匙点火(或一键启动)
- 你:只需要拧钥匙,不用关心各个系统如何配合工作
完整代码示例
场景:家庭影院系统
/*** 外观模式 - 家庭影院示例*/
public class Main {public static void main(String[] args) {System.out.println("=== 没有外观模式的痛苦 ===");withoutFacade();System.out.println("=== 使用外观模式的便捷 ===");withFacade();}// 没有外观模式:需要操作所有子系统public static void withoutFacade() {// 创建各个设备Amplifier amp = new Amplifier();DVDPlayer dvd = new DVDPlayer();Projector projector = new Projector();Screen screen = new Screen();Lights lights = new Lights();PopcornPopper popper = new PopcornPopper();// 手动一步步操作System.out.println("准备看电影...");popper.on();popper.pop();lights.dim(10);screen.down();projector.on();projector.setInput(dvd);projector.wideScreenMode();amp.on();amp.setDvd(dvd);amp.setSurroundSound();amp.setVolume(5);dvd.on();dvd.play("阿凡达");System.out.println("开始享受电影!\n");// 电影结束还要一个个关闭System.out.println("电影结束,关闭设备...");popper.off();lights.on();screen.up();projector.off();amp.off();dvd.stop();dvd.off();}// 使用外观模式:一键操作public static void withFacade() {HomeTheaterFacade homeTheater = new HomeTheaterFacade();// 一键观影homeTheater.watchMovie("阿凡达");// 一键结束homeTheater.endMovie();}
}/*** 复杂子系统 - 各个设备*/
class Amplifier {public void on() { System.out.println("放大器打开"); }public void off() { System.out.println("放大器关闭"); }public void setDvd(DVDPlayer dvd) { System.out.println("放大器设置DVD输入"); }public void setSurroundSound() { System.out.println("放大器设置环绕声"); }public void setVolume(int level) { System.out.println("放大器设置音量: " + level); }
}class DVDPlayer {public void on() { System.out.println("DVD播放器打开"); }public void off() { System.out.println("DVD播放器关闭"); }public void play(String movie) { System.out.println("DVD播放电影: " + movie); }public void stop() { System.out.println("DVD停止播放"); }
}class Projector {public void on() { System.out.println("投影仪打开"); }public void off() { System.out.println("投影仪关闭"); }public void setInput(DVDPlayer dvd) { System.out.println("投影仪设置DVD输入"); }public void wideScreenMode() { System.out.println("投影仪设置宽屏模式"); }
}class Screen {public void up() { System.out.println("屏幕升起"); }public void down() { System.out.println("屏幕降下"); }
}class Lights {public void on() { System.out.println("灯光打开"); }public void off() { System.out.println("灯光关闭"); }public void dim(int level) { System.out.println("灯光调暗到: " + level + "%"); }
}class PopcornPopper {public void on() { System.out.println("爆米花机打开"); }public void off() { System.out.println("爆米花机关闭"); }public void pop() { System.out.println("爆米花机开始爆米花"); }
}/*** 外观类 - 家庭影院外观*/
class HomeTheaterFacade {private Amplifier amp;private DVDPlayer dvd;private Projector projector;private Screen screen;private Lights lights;private PopcornPopper popper;public HomeTheaterFacade() {this.amp = new Amplifier();this.dvd = new DVDPlayer();this.projector = new Projector();this.screen = new Screen();this.lights = new Lights();this.popper = new PopcornPopper();}// 一键观影方法public void watchMovie(String movie) {System.out.println("准备观看电影: " + movie);popper.on();popper.pop();lights.dim(10);screen.down();projector.on();projector.setInput(dvd);projector.wideScreenMode();amp.on();amp.setDvd(dvd);amp.setSurroundSound();amp.setVolume(5);dvd.on();dvd.play(movie);System.out.println("开始享受电影!\n");}// 一键结束方法public void endMovie() {System.out.println("关闭家庭影院...");popper.off();lights.on();screen.up();projector.off();amp.off();dvd.stop();dvd.off();System.out.println("家庭影院已关闭");}// 还可以有其他便捷方法public void listenToMusic(String cd) {System.out.println("准备听音乐: " + cd);lights.on();amp.on();amp.setVolume(3);// 设置CD播放器等...System.out.println("开始欣赏音乐!");}
}
运行结果
=== 没有外观模式的痛苦 ===
准备看电影...
爆米花机打开
爆米花机开始爆米花
灯光调暗到: 10%
屏幕降下
投影仪打开
投影仪设置DVD输入
投影仪设置宽屏模式
放大器打开
放大器设置DVD输入
放大器设置环绕声
放大器设置音量: 5
DVD播放器打开
DVD播放电影: 阿凡达
开始享受电影!电影结束,关闭设备...
爆米花机关闭
灯光打开
屏幕升起
投影仪关闭
放大器关闭
DVD停止播放
DVD播放器关闭=== 使用外观模式的便捷 ===
准备观看电影: 阿凡达
爆米花机打开
爆米花机开始爆米花
灯光调暗到: 10%
屏幕降下
投影仪打开
投影仪设置DVD输入
投影仪设置宽屏模式
放大器打开
放大器设置DVD输入
放大器设置环绕声
放大器设置音量: 5
DVD播放器打开
DVD播放电影: 阿凡达
开始享受电影!关闭家庭影院...
爆米花机关闭
灯光打开
屏幕升起
投影仪关闭
放大器关闭
DVD停止播放
DVD播放器关闭
家庭影院已关闭
更实用的例子:计算机启动系统
/*** 计算机启动系统 - 外观模式示例*/
public class ComputerExample {public static void main(String[] args) {ComputerFacade computer = new ComputerFacade();// 一键开机computer.turnOn();System.out.println("\n使用计算机...\n");// 一关键机computer.turnOff();}
}/*** 复杂子系统 - 计算机各个组件*/
class CPU {public void freeze() { System.out.println("CPU冻结当前任务"); }public void jump(long position) { System.out.println("CPU跳转到引导程序位置: " + position); }public void execute() { System.out.println("CPU开始执行程序"); }
}class Memory {public void load(long position, byte[] data) { System.out.println("内存加载数据到位置: " + position); }
}class HardDrive {public byte[] read(long lba, int size) { System.out.println("硬盘读取扇区 " + lba + ", 大小: " + size + " bytes");return new byte[size];}
}class Display {public void show(String message) { System.out.println("显示器显示: " + message); }
}class PowerSupply {public void turnOn() { System.out.println("电源打开"); }public void turnOff() { System.out.println("电源关闭"); }
}/*** 外观类 - 计算机外观*/
class ComputerFacade {private CPU cpu;private Memory memory;private HardDrive hardDrive;private Display display;private PowerSupply powerSupply;// BIOS引导地址常量private static final long BOOT_ADDRESS = 0x0000;private static final long BOOT_SECTOR = 0x0010;private static final int SECTOR_SIZE = 1024;public ComputerFacade() {this.cpu = new CPU();this.memory = new Memory();this.hardDrive = new HardDrive();this.display = new Display();this.powerSupply = new PowerSupply();}// 一键开机public void turnOn() {System.out.println("=== 计算机启动过程 ===");powerSupply.turnOn();cpu.freeze();byte[] bootData = hardDrive.read(BOOT_SECTOR, SECTOR_SIZE);memory.load(BOOT_ADDRESS, bootData);cpu.jump(BOOT_ADDRESS);cpu.execute();display.show("欢迎使用计算机系统");System.out.println("计算机启动完成!");}// 一关键机public void turnOff() {System.out.println("=== 计算机关闭过程 ===");display.show("系统正在关闭...");cpu.freeze();powerSupply.turnOff();System.out.println("计算机关闭完成!");}// 重启功能public void restart() {System.out.println("=== 计算机重启 ===");turnOff();try {Thread.sleep(1000); // 等待1秒} catch (InterruptedException e) {e.printStackTrace();}turnOn();}
}
外观模式的核心结构
Client(客户端)↓Facade(外观)┌───────┼───────┐↓ ↓ ↓
Subsystem Subsystem SubsystemA B C
关键特征:
- 简化接口:为复杂子系统提供简单统一的接口
- 解耦:客户端与子系统解耦
- 封装:隐藏子系统的复杂性
- 入口点:通常是访问子系统的唯一入口
适用场景(大白话版)
✅ 适合用外观模式的场景:
-
复杂系统简化
// 比如框架的API、库的入口类 // Spring的ApplicationContext、JdbcTemplate都是外观 -
分层架构
// 服务层就是DAO层的外观 UserService userService = new UserService(); userService.register(user); // 内部调用多个DAO -
第三方库封装
// 封装复杂的第三方库,提供简单接口 PaymentFacade payment = new PaymentFacade(); payment.pay(amount); // 内部调用支付宝、微信等复杂API -
遗留系统现代化
// 为老系统提供新的简单接口 LegacySystemFacade facade = new LegacySystemFacade(); facade.modernOperation(); // 内部调用老系统的复杂操作
❌ 不适合的场景:
- 需要直接访问子系统:如果客户端需要直接使用子系统的特定功能
- 系统很简单:如果子系统本身就很简单的
- 需要高度灵活:如果客户端需要精细控制每个子系统组件
优缺点
优点:
- 简化使用:客户端使用起来非常简单
- 解耦:减少客户端与子系统的依赖
- 提高可维护性:子系统变化时,只需要修改外观类
- 更好的分层:明确系统边界
缺点:
- 不够灵活:如果客户端需要特殊功能,外观可能无法满足
- 成为上帝类:如果外观类承担太多职责,可能变得臃肿
- 增加层数:多了一层调用,有轻微性能损失
与其它模式对比
| 模式 | 目的 | 关键区别 |
|---|---|---|
| 外观模式 | 简化复杂系统接口 | 提供统一入口,隐藏复杂性 |
| 适配器模式 | 接口转换 | 解决接口不兼容问题 |
| 中介者模式 | 对象间通信 | 协调多个对象间的交互 |
| 单例模式 | 控制实例数量 | 确保只有一个实例,外观模式通常也用单例 |
实际应用案例
1. Spring框架
// ApplicationContext就是各种Bean工厂、配置系统等的外观
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyService service = context.getBean(MyService.class);
2. JDBC
// JdbcTemplate是Connection、Statement、ResultSet等的外观
JdbcTemplate template = new JdbcTemplate(dataSource);
template.query("SELECT * FROM users", rowMapper);
3. SLF4J日志门面
// SLF4J是Logback、Log4j等日志实现的外观
Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("Hello World");
总结
外观模式就是:
- 总指挥:协调各个部门完成复杂任务
- 一站式服务:一个窗口解决所有问题
- 简化界面:把复杂的操作流程封装成简单操作
核心口诀:
系统复杂接口多,
客户使用直挠头。
外观模式来封装,
简单接口解烦忧!
就像现实中的:
- 🏨 酒店前台:协调客房、餐饮、保洁等部门
- 🚗 汽车钥匙:一键启动复杂的发动机系统
- 🏥 医院导诊台:指导患者到正确的科室
- 📱 手机桌面:隐藏复杂的操作系统和硬件细节
记住:当你有一个复杂系统需要为客户端提供简单接口时,使用外观模式!
