跳到主要内容

外观模式(Facade)

外观模式是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

模式定义

外观模式(Facade Pattern):为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

核心要点

  1. 简化接口:提供一个简化的接口来访问复杂的子系统
  2. 解耦合:将客户端与子系统的复杂性解耦
  3. 分层设计:有助于建立分层系统
  4. 不封装子系统:客户端仍然可以直接访问子系统

问题场景

假设我们需要实现一个家庭影院系统,包含多个设备:投影仪、音响、DVD 播放器、屏幕、灯光等。要观看电影,需要执行一系列复杂的操作:

// 问题代码:客户端需要了解所有子系统
public class Client {
public static void main(String[] args) {
// 1. 打开灯光(调暗)
Light light = new Light();
light.dim(10);

// 2. 放下屏幕
Screen screen = new Screen();
screen.down();

// 3. 打开投影仪
Projector projector = new Projector();
projector.on();
projector.setInput("DVD");

// 4. 打开音响
Amplifier amp = new Amplifier();
amp.on();
amp.setVolume(5);
amp.setSurroundSound();

// 5. 打开 DVD 播放器
DvdPlayer dvd = new DvdPlayer();
dvd.on();
dvd.play("movie.dvd");

// 看完电影后,需要反向操作关闭所有设备...
}
}

存在的问题

  • 客户端需要了解所有子系统的接口
  • 操作顺序复杂,容易出错
  • 客户端与子系统高度耦合
  • 代码重复,每次看电影都要写一遍

解决方案

使用外观模式,创建一个家庭影院外观类,封装所有复杂的操作:

// 子系统类
public class Light {
public void on() { System.out.println("灯光打开"); }
public void off() { System.out.println("灯光关闭"); }
public void dim(int level) { System.out.println("灯光调暗到 " + level + "%"); }
}

public class Screen {
public void up() { System.out.println("屏幕升起"); }
public void down() { System.out.println("屏幕降下"); }
}

public class Projector {
public void on() { System.out.println("投影仪打开"); }
public void off() { System.out.println("投影仪关闭"); }
public void setInput(String input) { System.out.println("投影仪输入设置为 " + input); }
}

public 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("环绕声开启"); }
}

public 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("停止播放"); }
public void eject() { System.out.println("弹出光盘"); }
}

// 外观类
public class HomeTheaterFacade {
private Light light;
private Screen screen;
private Projector projector;
private Amplifier amplifier;
private DvdPlayer dvdPlayer;

public HomeTheaterFacade(Light light, Screen screen,
Projector projector, Amplifier amplifier,
DvdPlayer dvdPlayer) {
this.light = light;
this.screen = screen;
this.projector = projector;
this.amplifier = amplifier;
this.dvdPlayer = dvdPlayer;
}

// 看电影(简化接口)
public void watchMovie(String movie) {
System.out.println("=== 准备看电影 ===");
light.dim(10);
screen.down();
projector.on();
projector.setInput("DVD");
amplifier.on();
amplifier.setSurroundSound();
amplifier.setVolume(5);
dvdPlayer.on();
dvdPlayer.play(movie);
System.out.println("=== 开始观看 ===");
}

// 结束观看(简化接口)
public void endMovie() {
System.out.println("=== 结束观看 ===");
dvdPlayer.stop();
dvdPlayer.off();
amplifier.off();
projector.off();
screen.up();
light.on();
System.out.println("=== 电影结束 ===");
}

// 听音乐
public void listenToMusic() {
System.out.println("=== 准备听音乐 ===");
light.on();
amplifier.on();
amplifier.setVolume(3);
System.out.println("=== 开始播放音乐 ===");
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
// 创建子系统
Light light = new Light();
Screen screen = new Screen();
Projector projector = new Projector();
Amplifier amplifier = new Amplifier();
DvdPlayer dvdPlayer = new DvdPlayer();

// 创建外观
HomeTheaterFacade homeTheater = new HomeTheaterFacade(
light, screen, projector, amplifier, dvdPlayer
);

// 使用简化接口
homeTheater.watchMovie("复仇者联盟");
System.out.println();
homeTheater.endMovie();
}
}

输出

=== 准备看电影 ===
灯光调暗到 10%
屏幕降下
投影仪打开
投影仪输入设置为 DVD
音响打开
环绕声开启
音量设置为 5
DVD 播放器打开
播放电影: 复仇者联盟
=== 开始观看 ===

=== 结束观看 ===
停止播放
DVD 播放器关闭
音响关闭
投影仪关闭
屏幕升起
灯光打开
=== 电影结束 ===

模式结构

组成部分

  • Facade(外观):提供简化的接口,将客户端请求委派给相应的子系统
  • Subsystem(子系统):实现子系统的功能,处理外观对象分配的工作
  • Client(客户端):通过外观接口与子系统交互

实现方式

1. 基本实现

// 子系统 A
public class SubsystemA {
public void operationA() {
System.out.println("子系统 A 操作");
}
}

// 子系统 B
public class SubsystemB {
public void operationB() {
System.out.println("子系统 B 操作");
}
}

// 子系统 C
public class SubsystemC {
public void operationC() {
System.out.println("子系统 C 操作");
}
}

// 外观类
public class Facade {
private SubsystemA subsystemA;
private SubsystemB subsystemB;
private SubsystemC subsystemC;

public Facade() {
subsystemA = new SubsystemA();
subsystemB = new SubsystemB();
subsystemC = new SubsystemC();
}

// 简化操作 1
public void operation1() {
System.out.println("=== 外观操作 1 ===");
subsystemA.operationA();
subsystemB.operationB();
}

// 简化操作 2
public void operation2() {
System.out.println("=== 外观操作 2 ===");
subsystemB.operationB();
subsystemC.operationC();
}
}

// 使用
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation1();
facade.operation2();
}
}

2. 多层外观

可以为复杂的系统创建多层外观:

// 底层子系统
public class DatabaseService {
public void save(String data) { System.out.println("保存数据: " + data); }
public String load(String id) { return "数据-" + id; }
}

public class CacheService {
public void put(String key, String value) { System.out.println("缓存: " + key); }
public String get(String key) { return "缓存数据"; }
}

public class LogService {
public void log(String message) { System.out.println("日志: " + message); }
}

// 底层外观
public class DataAccessFacade {
private DatabaseService database;
private CacheService cache;

public DataAccessFacade() {
database = new DatabaseService();
cache = new CacheService();
}

public String getData(String id) {
String cached = cache.get(id);
if (cached != null) {
return cached;
}
String data = database.load(id);
cache.put(id, data);
return data;
}

public void saveData(String data) {
database.save(data);
}
}

// 高层外观
public class ApplicationFacade {
private DataAccessFacade dataAccess;
private LogService logger;

public ApplicationFacade() {
dataAccess = new DataAccessFacade();
logger = new LogService();
}

public void process(String id) {
logger.log("开始处理: " + id);
String data = dataAccess.getData(id);
logger.log("处理数据: " + data);
dataAccess.saveData("处理结果");
logger.log("处理完成");
}
}

3. 最小知识原则

外观模式符合最小知识原则(Law of Demeter):

// 违反最小知识原则
public class BadClient {
public void doSomething(HomeTheaterFacade facade) {
// 直接访问子系统
facade.getLight().dim(10);
facade.getScreen().down();
}
}

// 符合最小知识原则
public class GoodClient {
public void doSomething(HomeTheaterFacade facade) {
// 只与外观交互
facade.watchMovie("movie");
}
}

实际应用案例

1. JDBC 工具类

JDBC 操作数据库需要多个步骤,可以使用外观模式简化:

// JDBC 外观类
public class JdbcFacade {
private DataSource dataSource;

public JdbcFacade(DataSource dataSource) {
this.dataSource = dataSource;
}

// 简化查询
public <T> List<T> query(String sql, RowMapper<T> rowMapper, Object... params) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {

for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}

List<T> results = new ArrayList<>();
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
results.add(rowMapper.mapRow(rs));
}
}
return results;

} catch (SQLException e) {
throw new RuntimeException("查询失败", e);
}
}

// 简化更新
public int update(String sql, Object... params) {
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {

for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}
return stmt.executeUpdate();

} catch (SQLException e) {
throw new RuntimeException("更新失败", e);
}
}

// 行映射器接口
public interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
}

// 使用
public class UserDao {
private JdbcFacade jdbc;

public User findById(Long id) {
return jdbc.query(
"SELECT * FROM users WHERE id = ?",
rs -> new User(rs.getLong("id"), rs.getString("name")),
id
).get(0);
}
}

2. Spring 模板类

Spring 的 JdbcTemplateRestTemplate 等都是外观模式的应用:

// JdbcTemplate 简化了 JDBC 操作
@Repository
public class UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;

public User findById(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")),
id
);
}

public void save(User user) {
jdbcTemplate.update(
"INSERT INTO users (id, name) VALUES (?, ?)",
user.getId(), user.getName()
);
}
}

3. 操作系统启动

操作系统的启动过程是外观模式的典型应用:

// 子系统
public 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 执行"); }
}

public class Memory {
public void load(long position, byte[] data) {
System.out.println("内存加载: 位置 " + position);
}
}

public class HardDrive {
public byte[] read(long lba, int size) {
System.out.println("硬盘读取: LBA " + lba);
return new byte[size];
}
}

// 外观
public class ComputerFacade {
private Cpu cpu;
private Memory memory;
private HardDrive hardDrive;

public ComputerFacade() {
cpu = new Cpu();
memory = new Memory();
hardDrive = new HardDrive();
}

public void start() {
cpu.freeze();
memory.load(0, hardDrive.read(0, 1024));
cpu.jump(0);
cpu.execute();
}
}

// 使用
ComputerFacade computer = new ComputerFacade();
computer.start(); // 一键启动

4. 第三方 API 封装

封装复杂的第三方 API:

// 支付外观
public class PaymentFacade {
private AlipayService alipayService;
private WechatPayService wechatPayService;
private OrderService orderService;

public PaymentResult pay(String orderId, PaymentType type) {
// 1. 查询订单
Order order = orderService.getOrder(orderId);

// 2. 根据类型选择支付方式
PaymentResult result;
switch (type) {
case ALIPAY:
result = alipayService.pay(order);
break;
case WECHAT:
result = wechatPayService.pay(order);
break;
default:
throw new IllegalArgumentException("不支持的支付方式");
}

// 3. 更新订单状态
if (result.isSuccess()) {
orderService.updateStatus(orderId, OrderStatus.PAID);
}

return result;
}

public PaymentResult refund(String orderId) {
Order order = orderService.getOrder(orderId);
PaymentType type = order.getPaymentType();

PaymentResult result;
switch (type) {
case ALIPAY:
result = alipayService.refund(order);
break;
case WECHAT:
result = wechatPayService.refund(order);
break;
default:
throw new IllegalArgumentException("不支持的支付方式");
}

if (result.isSuccess()) {
orderService.updateStatus(orderId, OrderStatus.REFUNDED);
}

return result;
}
}

外观模式 vs 适配器模式

特性外观模式适配器模式
目的简化接口接口转换
接口数量定义新接口实现现有接口
复杂度隐藏复杂性解决不兼容
设计方式从零开始设计已有接口适配

外观模式 vs 中介者模式

特性外观模式中介者模式
方向单向(外观调用子系统)双向(中介者协调同事)
关注点简化接口对象交互
子系统不知道外观存在知道中介者存在

优缺点分析

优点

  1. 简化接口:将复杂的子系统调用封装成简单的方法
  2. 解耦合:客户端与子系统解耦,子系统变化不影响客户端
  3. 分层设计:有助于建立分层系统,降低系统复杂度
  4. 符合最小知识原则:客户端只需要知道外观类

缺点

  1. 可能成为上帝对象:外观类可能变得过于庞大
  2. 不封装子系统:客户端仍然可以直接访问子系统
  3. 新增功能困难:新增子系统可能需要修改外观类

使用建议

何时使用外观

  • 需要为复杂的子系统提供简单接口
  • 客户端与子系统之间存在很多依赖
  • 需要对子系统进行分层
  • 需要封装第三方库的复杂性

何时避免使用外观

  • 子系统已经足够简单
  • 客户端需要直接访问子系统的特定功能
  • 外观类会变得过于复杂

最佳实践

  1. 保持外观简单:外观只做委托,不添加业务逻辑
  2. 不限制访问:允许客户端直接访问子系统
  3. 合理命名:外观类名应体现其功能
  4. 考虑多层外观:复杂系统可以使用多层外观

小结

外观模式是一种简单但非常实用的结构型模式:

应用场景示例
简化复杂接口家庭影院系统
封装第三方库JDBC 工具类
分层设计应用架构
统一入口支付系统

练习

  1. 实现一个文件操作外观类,封装文件的读取、写入、复制等操作
  2. 实现一个邮件发送外观类,封装 SMTP 协议的复杂性
  3. 分析 Spring 的 JdbcTemplate 如何简化 JDBC 操作
  4. 实现一个日志外观类,支持多种日志框架

参考资源