模板回调模式:让模板方法更灵活
说实话,模板方法模式大家都不陌生。父类定义骨架,子类实现细节,很经典的套路。但我在实际项目中慢慢发现,继承的方式有时候太「重」了——为了一个回调方法,你得专门写个子类,用起来不够爽利。
模板回调模式,说白了就是「模板方法 + 回调接口」的组合拳。它用组合代替继承,用匿名实现类代替子类。嗯,Spring 的 JdbcTemplate 就是教科书级的案例。
从模板方法到模板回调
先看一个典型的模板方法例子:
// 模板方法风格
public abstract class DataProcessor {
public final void process() {
loadData();
processData();
saveData();
}
protected abstract void processData();
// 其他方法...
}
// 使用:得写个子类
public class UserProcessor extends DataProcessor {
@Override
protected void processData() {
// 业务逻辑
}
}
你想想看,每次用都要 new 一个子类,或者写个匿名内部类。如果只是改一小段逻辑,这代价有点大。
模板回调模式怎么改?把抽象方法抽成接口:
// 回调接口
public interface DataCallback {
void processData();
}
// 模板类
public class DataTemplate {
public final void execute(DataCallback callback) {
loadData();
callback.processData(); // 回调
saveData();
}
private void loadData() { /* ... */ }
private void saveData() { /* ... */ }
}
// 使用:直接传 Lambda 或匿名类
DataTemplate template = new DataTemplate();
template.execute(() -> {
// 业务逻辑,想写啥写啥
});
我个人习惯用这种方式。它把「变化的部分」通过参数传进去,而不是通过继承固定下来。灵活得多。
回调接口的设计要点
设计回调接口时,有几个坑我曾经踩过:
- 接口粒度要适中——太粗了没法复用,太细了调用方写起来累
- 考虑异常处理——回调里抛异常怎么办?模板要不要 catch?
- 上下文传递——回调可能需要访问模板内部的临时数据
JdbcTemplate:模板回调的经典案例
Spring 的 JdbcTemplate 是模板回调模式的集大成者。它把 JDBC 的「获取连接→创建语句→执行→处理结果→关闭资源」这个固定流程封装起来,只把「处理结果集」这一块交给回调。
核心接口就两个:
| 接口 | 用途 | 典型场景 |
|---|---|---|
| RowMapper<T> | 将 ResultSet 的一行映射为对象 | 查询单条或多条记录 |
| PreparedStatementCallback<T> | 自定义 PreparedStatement 操作 | 批量更新、存储过程调用 |
看个实际用法:
// RowMapper 回调
List<User> users = jdbcTemplate.query(
"SELECT * FROM users WHERE age > ?",
new Object[]{18},
(rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
return user;
}
);
// PreparedStatementCallback 回调
Integer count = jdbcTemplate.execute(
"UPDATE users SET status = ? WHERE id = ?",
(PreparedStatement ps) -> {
ps.setInt(1, 1);
ps.setLong(2, 100L);
return ps.executeUpdate();
}
);
你看,JdbcTemplate 把连接管理、异常转换、资源释放全包了。你只需要关心「怎么处理数据」这一件事。这就是模板回调的魅力。
核心逻辑流程图
下面这张图展示了模板回调模式的核心流程:
自己实现一个模板回调
光看不练假把式。咱们自己写一个简单的文件处理模板:
// 回调接口
@FunctionalInterface
public interface FileProcessorCallback {
void process(BufferedReader reader) throws IOException;
}
// 模板类
public class FileTemplate {
public void execute(String filePath, FileProcessorCallback callback) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(filePath));
callback.process(reader); // 回调
} catch (IOException e) {
// 统一异常处理
throw new RuntimeException("文件处理失败: " + filePath, e);
} finally {
if (reader != null) {
try { reader.close(); } catch (IOException ignored) {}
}
}
}
}
// 使用
FileTemplate template = new FileTemplate();
template.execute("data.txt", reader -> {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
});
模板回调 vs 模板方法:怎么选?
很多同学会问:什么时候用模板回调,什么时候用模板方法?我个人的经验是:
- 用模板方法:当你有多个子类共享大部分逻辑,且子类之间有明显的「is-a」关系
- 用模板回调:当变化点很零散,每次调用都可能不同,或者你不想为了一个方法就建个子类
说白了,模板回调更适合「一次性」或「轻量级」的场景。JdbcTemplate 就是典型——你不可能为每个 SQL 查询都写个子类吧?
避坑指南
还有一点要注意:回调里的资源管理。如果回调里打开了额外的资源(比如另一个文件流),记得在回调内部关闭。模板只负责它自己的资源,不负责你的。
嗯,模板回调模式就聊到这儿。它不是什么高深的概念,但用好了能让代码灵活不少。下次你写模板方法的时候,不妨想想:能不能用回调接口代替抽象方法?
公众号:蓝海资料掘金营,微信deep3321