第12章:外观模式(Facade)—— 给复杂系统一个“简化接口”
外观模式,说白了就是“封装复杂性,提供简单接口”。
我刚开始接触设计模式时,觉得外观模式太简单了,不就是加个中间层吗?后来在项目中踩过坑才明白——外观模式是系统分层的“守门员”,它决定了你的代码是优雅还是混乱。
12.1 外观模式的结构
外观模式的核心思想:为子系统中的一组接口提供一个统一的、更高级的接口。它让子系统更容易使用,也降低了客户端与子系统的耦合。
结构上,外观模式包含三个角色:
- 外观(Facade):对外提供简化方法,内部调用子系统的复杂逻辑。
- 子系统(Subsystem):一组完成具体功能的类,它们之间可能有复杂的依赖关系。
- 客户端(Client):只与外观交互,不直接操作子系统。
我画了一张结构图,帮你直观理解:
你看,客户端只跟外观打交道。外观把子系统A、B、C的调用顺序和依赖关系都封装起来了。客户端调用一个方法,外观帮你搞定一切。
核心要点:外观模式不是“添加功能”,而是“简化访问”。它不限制客户端直接访问子系统,但提供了更便捷的路径。
12.2 外观模式的适用场景
我个人习惯在以下场景使用外观模式:
- 系统分层:每一层对外只暴露一个外观类,内部实现可以随意重构。
- 第三方库封装:比如对接支付网关,把复杂的签名、加密、回调处理封装成一个外观。
- 遗留系统改造:老系统接口混乱,加个外观层让新模块调用,不用动老代码。
- 启动/关闭流程:比如游戏引擎初始化,需要加载资源、创建窗口、初始化网络……外观一个方法搞定。
我记得有一次重构一个电商订单系统,下单流程涉及库存、支付、物流、优惠券四个子系统。原来客户端代码里直接调了十几个方法,顺序错了就出bug。我加了个OrderFacade,客户端只调placeOrder(),内部按正确顺序调用子系统。嗯,代码瞬间清爽了。
12.3 外观模式在Spring JDBC中的应用:JdbcTemplate
说到外观模式的经典案例,Spring的JdbcTemplate绝对排得上号。
你想想看,原生JDBC操作数据库有多麻烦:
- 加载驱动
- 获取连接
- 创建Statement/PreparedStatement
- 设置参数
- 执行SQL
- 处理ResultSet
- 关闭连接、Statement、ResultSet
- 处理异常
这还只是查询一条数据。如果每个方法都这么写,项目里全是重复的模板代码。而且你很容易忘记关闭连接,导致连接泄漏——我曾经就因为这个被DBA找过谈话。
JdbcTemplate就是JDBC操作的外观。它把上面那些步骤全部封装起来,对外只提供几个简洁的方法:
// 原生JDBC —— 你不想写的代码
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, userId);
rs = ps.executeQuery();
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
// ... 其他字段
return user;
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (rs != null) try { rs.close(); } catch (SQLException e) { /* 忽略 */ }
if (ps != null) try { ps.close(); } catch (SQLException e) { /* 忽略 */ }
if (conn != null) try { conn.close(); } catch (SQLException e) { /* 忽略 */ }
}
// JdbcTemplate —— 你真正想写的代码
User user = jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new Object[]{userId},
(rs, rowNum) -> {
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
return u;
}
);
看到了吗?JdbcTemplate把连接管理、异常处理、资源释放全部隐藏了。你只需要关心SQL和结果映射。
小提示:JdbcTemplate内部还做了很多细节优化,比如预编译语句缓存、批量操作优化、连接延迟获取等。这些你都不用管,外观帮你搞定了。
12.4 JdbcTemplate的外观角色分析
我们来拆解一下JdbcTemplate的外观角色:
| 角色 | 对应类/组件 | 说明 |
|---|---|---|
| 外观(Facade) | JdbcTemplate | 对外提供query、update、execute等简化方法 |
| 子系统 | DataSource、Connection、Statement、ResultSet、SQLException | JDK原生API,复杂且易出错 |
| 客户端 | 你的DAO层代码 | 只调用JdbcTemplate,不直接操作JDBC |
JdbcTemplate内部还使用了回调模式(比如PreparedStatementCallback、ResultSetExtractor),让客户端可以自定义部分逻辑。但外观的职责不变——封装通用流程,暴露定制点。
避坑指南:我曾经见过有人直接在JdbcTemplate外面再包一层外观,然后又包一层……最后整个项目有四五层外观。记住,外观模式不是越多越好。一层外观足够,多了反而增加调试难度。
12.5 外观模式的优缺点
优点很明显:
- 简化使用:客户端代码量减少50%以上
- 降低耦合:客户端不依赖子系统,子系统变化不影响客户端
- 提高安全性:可以限制客户端对某些子系统的直接访问
缺点也要心里有数:
- 外观可能变成“上帝类”:如果外观包含了太多子系统的逻辑,它会变得臃肿且难以维护
- 不够灵活:客户端如果需要绕过外观直接操作子系统,外观就成了障碍
怎么解决?我的经验是:外观只做“编排”,不做“业务”。外观负责调用子系统的顺序和异常处理,但具体的业务逻辑应该留在子系统里。这样外观不会膨胀,子系统也能独立测试。
12.6 总结
外观模式,说白了就是“给复杂系统一个简单入口”。
JdbcTemplate是教科书级别的案例——它把JDBC的繁琐细节全部封装,让开发者专注于SQL和结果处理。你在项目中遇到复杂的初始化流程、多步骤操作、第三方库集成时,都可以考虑引入外观模式。
但记住:外观不是万能药。它适合“简化访问”,不适合“增加功能”。如果子系统本身就不复杂,强行加外观反而画蛇添足。
一句话总结:外观模式让复杂系统变得“好用”,但设计外观时,请保持克制——只封装,不膨胀。
公众号:蓝海资料掘金营,微信deep3321