模板方法模式
问题
什么是模板方法模式?Java 和 Spring 中有哪些典型应用?
答案
核心思想
在父类中定义算法的骨架(步骤顺序),将某些步骤的具体实现延迟到子类。子类可以重写特定步骤,但不能改变整体流程。
实现示例
模板方法 - 数据导出
public abstract class DataExporter {
// 模板方法:定义导出流程(final 防止子类修改流程)
public final void export() {
List<Map<String, Object>> data = queryData(); // 第 1 步:查数据
List<String[]> rows = transformData(data); // 第 2 步:转换格式
byte[] file = generateFile(rows); // 第 3 步:生成文件(子类实现)
if (needCompress()) { // 钩子方法:是否压缩
file = compress(file);
}
upload(file); // 第 4 步:上传
}
// 通用步骤:父类已实现
protected List<Map<String, Object>> queryData() { /* SQL 查询 */ return List.of(); }
protected void upload(byte[] file) { /* OSS 上传 */ }
// 抽象步骤:子类必须实现
protected abstract byte[] generateFile(List<String[]> rows);
// 钩子方法:子类可选覆盖,默认不压缩
protected boolean needCompress() { return false; }
}
// Excel 导出
public class ExcelExporter extends DataExporter {
@Override
protected byte[] generateFile(List<String[]> rows) {
// 使用 Apache POI 生成 Excel
return new byte[0];
}
}
// CSV 导出 + 压缩
public class CsvExporter extends DataExporter {
@Override
protected byte[] generateFile(List<String[]> rows) {
// 生成 CSV 文件
return new byte[0];
}
@Override
protected boolean needCompress() { return true; } // 覆盖钩子
}
Java/Spring 中的典型应用
| 应用 | 模板方法 | 子类/回调 |
|---|---|---|
AbstractQueuedSynchronizer | acquire/release | tryAcquire/tryRelease |
AbstractList | iterator() | get(int index) |
HttpServlet | service() | doGet()/doPost() |
JdbcTemplate | execute() | RowMapper/ResultSetExtractor |
RedisTemplate | execute() | RedisCallback |
RestTemplate | 请求流程 | ResponseExtractor |
JdbcTemplate 中的模板方法
// JdbcTemplate 封装了获取连接、执行SQL、处理异常、关闭资源的流程
// 用户只需提供 SQL 和结果映射逻辑
List<User> users = jdbcTemplate.query(
"SELECT * FROM users WHERE age > ?",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")), // 回调
18
);
模板方法 vs 策略模式
- 模板方法:通过继承改变算法的部分步骤,流程固定
- 策略模式:通过组合替换整个算法
- JdbcTemplate 使用回调(类似策略模式)代替继承,更灵活
常见面试问题
Q1: AQS 中的模板方法模式?
答案:
AbstractQueuedSynchronizer 定义了加锁/解锁的流程(acquire/release),但将具体的锁逻辑(tryAcquire/tryRelease)留给子类实现。
ReentrantLock:可重入锁CountDownLatch:倒计数Semaphore:信号量
详见 Lock 接口与 AQS。
Q2: 模板方法模式的缺点?
答案:
- 类爆炸:每个变体一个子类
- 继承耦合:子类和父类绑定,父类修改可能影响所有子类
- 不够灵活:流程固定,无法在运行时切换
现代 Java 更倾向用回调/Lambda 代替继承(如 JdbcTemplate 的 RowMapper),既保留模板方法的流程控制,又避免继承耦合。