以下是为你精心撰写的 《Java 模板方法模式深度学习指南》,专为 Java 后端开发者打造,内容涵盖:定义、作用、与策略模式的深度对比、真实业务场景、实现方式、Spring 集成、避坑指南、最佳实践、面试高频题,所有示例均含中文注释,可直接用于项目重构、订单流程、审批流、数据导入导出、报表生成等核心场景。
📘 Java 模板方法模式深度学习指南
—— 从“重复流程”到“固定骨架,灵活实现”的架构升华
作者:Java 后端架构实战导师
适用对象:Java 后端开发者(Spring Boot / 微服务 / 订单系统 / 审批流 / 数据处理)
目标:彻底掌握“定义算法骨架,让子类重写具体步骤”的经典设计模式,告别“复制粘贴”和“流程混乱”
核心原则:“我规定你必须按这个流程走,但每一步你怎么干,你说了算。”
✅ 一、什么是模板方法模式?
📌 定义:
模板方法模式(Template Method Pattern) 是一种行为型设计模式,它定义了一个算法的骨架(即固定的执行流程),并将一些具体步骤延迟到子类中实现。子类可以重写这些步骤,但不改变算法的整体结构。
🔍 核心思想:
- 把一个固定流程(如:下单 → 扣库存 → 支付 → 发货 → 通知)封装在父类中
- 将流程中可变的部分(如:支付方式、通知方式)定义为抽象方法或钩子方法
- 子类继承父类,重写具体步骤,实现自己的业务逻辑
- 客户端调用父类的模板方法,自动执行完整流程
💡 一句话记忆:
“我给你一个标准流程,你按这个顺序做,但每一步怎么做,你自己决定。”
🆚 模板方法模式 vs 策略模式(极易混淆)
| 维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 目的 | 固定流程,灵活步骤 | 固定接口,灵活算法 |
| 控制权 | 父类控制流程,子类控制步骤 | 客户端控制选择哪个算法 |
| 实现方式 | 继承(Is-A) | 组合(Has-A) |
| 调用方式 | 客户端调用父类方法 | 客户端调用策略对象 |
| 是否可变流程 | ❌ 流程固定 | ✅ 算法可变,流程可换 |
| 典型场景 | 订单流程、审批流、数据导入 | 支付方式、排序算法、折扣计算 |
| 类比 | 做饭流程:洗菜 → 切菜 → 炒菜 → 装盘(你决定炒什么菜) | 你选择用锅炒、用电饭锅煮、用微波炉热 |
✅ 关键区别:
- 模板方法:“你必须按我的流程走,但每一步你自由发挥”
- 策略模式:“你可以选不同的流程,但每个流程的接口是一样的”
✅ 经典比喻:
你去餐厅吃饭:
- 模板方法:餐厅规定“先点菜 → 等待上菜 → 吃饭 → 结账”,但“吃什么菜”你决定
- 策略模式:你可以选“中餐”、“西餐”、“日料”三种不同流程
✅ 二、模板方法模式有什么作用?(Why Use Template Method?)
| 作用 | 说明 |
|---|---|
| ✅ 避免代码重复 | 公共流程只写一次,子类复用 |
| ✅ 强制流程规范 | 确保所有子类都按统一顺序执行关键步骤 |
| ✅ 提高可维护性 | 修改流程只需改父类,影响所有子类 |
| ✅ 支持开闭原则 | 新增流程变体只需新增子类,不改父类 |
| ✅ 增强可读性 | 流程结构清晰,一目了然 |
| ✅ 支持钩子方法 | 子类可选择性覆盖某些步骤,实现灵活扩展 |
| ✅ 便于测试 | 可对父类流程做集成测试,对子类步骤做单元测试 |
| ✅ Spring 集成基础 | Spring 的 AbstractApplicationContext、JdbcTemplate 都是模板方法模式 |
💡 经典名言:
“Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.”
——《Design Patterns: Elements of Reusable Object-Oriented Software》
✅ 三、模板方法模式的典型使用场景(Java 后端真实案例)
| 场景 | 说明 | 是否推荐使用模板方法模式 |
|---|---|---|
| ✅ 订单创建流程 | 校验 → 扣库存 → 支付 → 生成物流 → 发送通知 | ✅ 强烈推荐 |
| ✅ 用户注册流程 | 验证邮箱 → 发送验证码 → 创建账户 → 绑定手机号 → 发送欢迎邮件 | ✅ 强烈推荐 |
| ✅ 审批流程 | 提交 → 部门审核 → 财务审核 → 总监审批 → 归档 | ✅ 强烈推荐 |
| ✅ 数据导入 | 读取文件 → 校验格式 → 解析数据 → 验证业务规则 → 批量入库 → 生成报告 | ✅ 推荐 |
| ✅ 数据导出 | 查询数据 → 格式化 → 生成文件 → 发送邮件/下载 | ✅ 推荐 |
| ✅ 报表生成 | 连接数据库 → 查询数据 → 计算指标 → 生成图表 → 输出PDF/Excel | ✅ 推荐 |
| ✅ 任务调度 | 准备资源 → 执行任务 → 检查结果 → 记录日志 → 发送通知 | ✅ 推荐 |
| ✅ 缓存预热 | 加载配置 → 初始化连接 → 预加载数据 → 启动定时任务 | ✅ 推荐 |
| ✅ 微服务启动 | 加载配置 → 初始化组件 → 注册服务 → 开启端口 → 启动健康检查 | ✅ 推荐 |
| ❌ 支付方式选择 | 支付宝、微信、银联,只是算法不同 | ❌ 用策略模式 |
| ❌ 排序算法 | 快排、冒泡、归并 | ❌ 用策略模式 |
| ❌ 单一操作 | 只有“保存用户”一个动作 | ❌ 直接写方法 |
✅ 判断标准:
“有一个固定流程,包含多个步骤,其中部分步骤的实现因业务而异?”
→ 是 → 用模板方法模式!
✅ 四、模板方法模式的三种实现方式详解(含中文注释)
我们从基础结构到Spring 模板类,逐步深入。
🔹 1. 基础结构:抽象类 + 抽象方法 + 钩子方法
/**
* 【1】抽象模板类:定义算法骨架
*/
public abstract class OrderProcessTemplate {
/**
* 【核心模板方法】:定义固定流程,final 防止被重写
*/
public final void processOrder(String orderId) {
System.out.println("📦 开始处理订单:" + orderId);
validateOrder(orderId); // 步骤1:校验订单
calculateTotal(orderId); // 步骤2:计算金额
applyDiscount(orderId); // 步骤3:应用折扣(子类实现)
saveOrder(orderId); // 步骤4:保存订单
notifyUser(orderId); // 步骤5:通知用户
System.out.println("✅ 订单处理完成\n");
}
/**
* 【公共方法】:所有子类共享的逻辑
*/
protected void validateOrder(String orderId) {
System.out.println("🔧 步骤1:校验订单是否存在");
}
protected void calculateTotal(String orderId) {
System.out.println("💰 步骤2:计算订单总金额");
}
/**
* 【抽象方法】:子类必须实现(强制步骤)
*/
protected abstract void applyDiscount(String orderId);
/**
* 【公共方法】:通用逻辑
*/
protected void saveOrder(String orderId) {
System.out.println("💾 步骤4:保存订单到数据库");
}
/**
* 【钩子方法】:子类可选择性重写(非强制)
*/
protected void notifyUser(String orderId) {
System.out.println("📢 步骤5:发送短信通知用户");
}
// 可添加更多钩子方法
protected boolean isExpressDelivery(String orderId) {
return false; // 默认不加急
}
}
/**
* 【2】具体子类1:普通订单
*/
class RegularOrderProcess extends OrderProcessTemplate {
@Override
protected void applyDiscount(String orderId) {
System.out.println("📉 步骤3:普通订单无折扣");
// 不打折
}
// 可选择性重写钩子方法
@Override
protected boolean isExpressDelivery(String orderId) {
return true; // 普通订单也加急
}
@Override
protected void notifyUser(String orderId) {
System.out.println("📱 步骤5:发送短信 + 邮件通知用户");
}
}
/**
* 【3】具体子类2:VIP订单
*/
class VIPOrderProcess extends OrderProcessTemplate {
@Override
protected void applyDiscount(String orderId) {
System.out.println("🎁 步骤3:VIP订单享受9折优惠");
// 9折
}
// 不重写钩子方法,使用默认实现
}
/**
* 【4】客户端:调用模板方法
*/
public class TemplateMethodDemo {
public static void main(String[] args) {
// 创建普通订单流程
OrderProcessTemplate regular = new RegularOrderProcess();
regular.processOrder("ORD001");
// 创建VIP订单流程
OrderProcessTemplate vip = new VIPOrderProcess();
vip.processOrder("ORD002");
}
}
✅ 输出结果:
📦 开始处理订单:ORD001
🔧 步骤1:校验订单是否存在
💰 步骤2:计算订单总金额
📉 步骤3:普通订单无折扣
💾 步骤4:保存订单到数据库
📱 步骤5:发送短信 + 邮件通知用户
✅ 订单处理完成
📦 开始处理订单:ORD002
🔧 步骤1:校验订单是否存在
💰 步骤2:计算订单总金额
🎁 步骤3:VIP订单享受9折优惠
💾 步骤4:保存订单到数据库
📢 步骤5:发送短信通知用户
✅ 订单处理完成
✅ 优点:
- 流程固定:保证所有订单都走“校验 → 计算 → 折扣 → 保存 → 通知”流程
- 可扩展:新增“企业订单”只需新建子类
- 复用性强:公共逻辑(validate、save)只写一次
- 钩子灵活:
isExpressDelivery()可选重写,不强制
⚠️ 注意:
- 模板方法必须是
final,防止子类修改流程顺序 - 抽象方法必须由子类实现
- 钩子方法有默认实现,子类可选覆盖
🔹 2. 模板方法模式 + Spring 模板类(企业级实战)
Spring 的
JdbcTemplate、RestTemplate、TransactionTemplate都是模板方法模式的典范!
/**
* 【1】抽象模板类:模拟 JdbcTemplate
*/
public abstract class DatabaseTemplate {
/**
* 模板方法:执行数据库操作
*/
public final <T> T execute(String sql, RowMapper<T> rowMapper) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
// 1. 获取连接
conn = getConnection();
System.out.println("🔌 获取数据库连接");
// 2. 准备语句
stmt = conn.prepareStatement(sql);
System.out.println("📝 准备SQL语句:" + sql);
// 3. 执行查询
rs = stmt.executeQuery();
System.out.println("🔍 执行查询");
// 4. 映射结果(交给子类实现)
T result = mapRow(rs, rowMapper);
System.out.println("📊 映射结果");
return result;
} catch (Exception e) {
System.err.println("❌ 数据库操作失败:" + e.getMessage());
throw new RuntimeException("数据库异常", e);
} finally {
// 5. 关闭资源(固定清理)
closeResources(conn, stmt, rs);
System.out.println("🗑️ 关闭数据库连接");
}
}
/**
* 【抽象方法】:子类负责映射结果
*/
protected abstract <T> T mapRow(ResultSet rs, RowMapper<T> rowMapper);
/**
* 【公共方法】:获取连接
*/
protected Connection getConnection() {
// 实际项目中:从连接池获取
return new Connection() { // 模拟
@Override
public PreparedStatement prepareStatement(String sql) {
return new PreparedStatement() {
@Override
public ResultSet executeQuery() {
// 模拟返回结果集
return new ResultSet() {
@Override
public boolean next() {
return true; // 有数据
}
@Override
public String getString(String columnLabel) {
return "张三";
}
};
}
};
}
};
}
/**
* 【公共方法】:关闭资源
*/
protected void closeResources(Connection conn, PreparedStatement stmt, ResultSet rs) {
// 真实项目中:try-with-resources 或工具类关闭
}
}
/**
* 【2】结果映射器接口
*/
interface RowMapper<T> {
T mapRow(ResultSet rs) throws SQLException;
}
/**
* 【3】具体实现:用户查询模板
*/
class UserQueryTemplate extends DatabaseTemplate {
@Override
protected <T> T mapRow(ResultSet rs, RowMapper<T> rowMapper) {
// 调用传入的 RowMapper
return (T) rowMapper.mapRow(rs);
}
/**
* 提供便捷方法:查询单个用户
*/
public User findUserById(Long id) {
String sql = "SELECT * FROM users WHERE id = ?";
return execute(sql, rs -> {
if (rs.next()) {
return new User(rs.getLong("id"), rs.getString("name"), rs.getString("email"));
}
return null;
});
}
}
/**
* 【4】数据对象
*/
record User(Long id, String name, String email) {}
/**
* 【5】客户端使用
*/
public class SpringTemplateDemo {
public static void main(String[] args) {
UserQueryTemplate template = new UserQueryTemplate();
// ✅ 客户端只关心“我要查什么”,不关心连接、关闭、异常
User user = template.findUserById(1L);
System.out.println("👤 查询结果:" + user);
// 输出:
// 🔌 获取数据库连接
// 📝 准备SQL语句:SELECT * FROM users WHERE id = ?
// 🔍 执行查询
// 📊 映射结果
// 🗑️ 关闭数据库连接
// 👤 查询结果:User[id=1, name=张三, email=zhangsan@xx.com]
}
}
✅ 优点:
- 完全解耦:客户端只写 SQL 和映射逻辑
- 资源安全:连接自动关闭,防止泄漏
- 异常统一:所有异常包装为
RuntimeException - Spring 模板类:
JdbcTemplate、RestTemplate、TransactionTemplate都是这样设计的!
🔹 3. 模板方法模式 + Spring Boot 自动配置(高级实战)
在 Spring Boot 中,自动配置类 就是模板方法模式的体现!
/**
* 【1】抽象模板:定义数据导入流程
*/
public abstract class DataImportTemplate {
/**
* 模板方法:固定导入流程
*/
public final void importData(String filePath) {
System.out.println("📥 开始导入数据文件:" + filePath);
// 步骤1:读取文件
List<String> lines = readFile(filePath);
System.out.println("📄 读取文件行数:" + lines.size());
// 步骤2:解析每一行(子类实现)
List<DataRecord> records = parseLines(lines);
System.out.println("🧩 解析数据记录数:" + records.size());
// 步骤3:校验数据(子类实现)
validateRecords(records);
System.out.println("✅ 数据校验通过");
// 步骤4:批量保存(子类实现)
saveRecords(records);
System.out.println("💾 批量保存成功");
// 步骤5:发送通知(钩子方法)
sendNotification(records.size());
System.out.println("📢 通知发送完成");
System.out.println("🎉 数据导入完成\n");
}
// 公共方法:读取文件(固定)
protected List<String> readFile(String filePath) {
// 模拟读取
return List.of("张三,13800138000,zhangsan@xx.com", "李四,13900139000,lisi@xx.com");
}
// 抽象方法:子类实现解析逻辑
protected abstract List<DataRecord> parseLines(List<String> lines);
// 抽象方法:子类实现校验逻辑
protected abstract void validateRecords(List<DataRecord> records);
// 抽象方法:子类实现保存逻辑
protected abstract void saveRecords(List<DataRecord> records);
// 钩子方法:可选重写
protected void sendNotification(int count) {
System.out.println("📧 发送邮件通知:成功导入 " + count + " 条记录");
}
}
/**
* 【2】数据记录对象
*/
record DataRecord(String name, String phone, String email) {}
/**
* 【3】具体实现1:导入用户数据
*/
@Component
public class UserImportTemplate extends DataImportTemplate {
@Autowired
private UserService userService;
@Override
protected List<DataRecord> parseLines(List<String> lines) {
return lines.stream()
.map(line -> {
String[] parts = line.split(",");
return new DataRecord(parts[0], parts[1], parts[2]);
})
.toList();
}
@Override
protected void validateRecords(List<DataRecord> records) {
for (DataRecord record : records) {
if (record.name().isEmpty() || record.phone().isEmpty()) {
throw new IllegalArgumentException("用户信息不完整:" + record);
}
}
}
@Override
protected void saveRecords(List<DataRecord> records) {
records.forEach(record -> {
userService.createUser(record.name(), record.phone(), record.email());
});
}
// 重写钩子方法:发送短信通知
@Override
protected void sendNotification(int count) {
System.out.println("📱 发送短信通知:成功导入 " + count + " 个用户");
}
}
/**
* 【4】用户服务
*/
@Service
class UserService {
public void createUser(String name, String phone, String email) {
System.out.println("👤 创建用户:" + name + ", 手机:" + phone + ", 邮箱:" + email);
}
}
/**
* 【5】启动类
*/
@SpringBootApplication
public class TemplateBootDemo implements CommandLineRunner {
@Autowired
private UserImportTemplate userImportTemplate;
public static void main(String[] args) {
SpringApplication.run(TemplateBootDemo.class, args);
}
@Override
public void run(String... args) throws Exception {
userImportTemplate.importData("users.csv");
}
}
✅ 输出:
📥 开始导入数据文件:users.csv
📄 读取文件行数:2
🧩 解析数据记录数:2
✅ 数据校验通过
👤 创建用户:张三, 手机:13800138000, 邮箱:zhangsan@xx.com
👤 创建用户:李四, 手机:13900139000, 邮箱:lisi@xx.com
💾 批量保存成功
📱 发送短信通知:成功导入 2 个用户
📢 通知发送完成
🎉 数据导入完成
✅ 优点:
- 可插拔:新增“商品导入”只需新建
ProductImportTemplate - Spring 管理:自动注入
UserService - 配置驱动:可通过
@Profile控制不同环境使用不同模板
✅ 五、模板方法模式 vs 策略模式 对比总结表
| 维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 目的 | 固定流程,灵活步骤 | 固定接口,灵活算法 |
| 实现方式 | 继承(Is-A) | 组合(Has-A) |
| 控制权 | 父类控制流程,子类控制步骤 | 客户端控制选择哪个策略 |
| 调用方式 | 客户端调用父类模板方法 | 客户端调用策略对象的方法 |
| 灵活性 | 流程固定,步骤可变 | 流程可换,算法固定 |
| 典型场景 | 订单流程、审批流、数据导入 | 支付方式、排序算法、折扣策略 |
| Spring 实现 | JdbcTemplate、TransactionTemplate | Comparator、MessageConverter |
✅ 一句话区分:
- 模板方法:“你必须按这个流程走,但每一步你自由发挥”
- 策略模式:“你可以选不同的流程,但每个流程的接口是一样的”
✅ 六、模板方法模式的避坑指南(Java 后端高频踩坑)
| 问题 | 原因 | 解决方案 |
|---|---|---|
| ❌ 模板方法不是 final | 子类重写流程顺序 | ✅ 所有模板方法必须声明为 final |
| ❌ 抽象方法写成具体方法 | 子类无法重写 | ✅ 需要子类实现的必须是 abstract |
| ❌ 钩子方法没有默认实现 | 子类必须实现 | ✅ 钩子方法应有默认空实现 |
| ❌ 在模板方法中调用子类的非抽象方法 | 可能调用未初始化对象 | ✅ 只调用 final、abstract、protected 方法 |
| ❌ 模板方法中包含业务逻辑 | 违反单一职责 | ✅ 模板方法只控制流程,业务逻辑放子类 |
| ❌ 使用模板方法做“单一操作” | 过度设计 | ✅ 至少 3 个步骤,且有可变部分才用 |
✅ 七、学习建议与进阶路径
| 阶段 | 建议 |
|---|---|
| 📚 第一周 | 将你项目中的“用户注册流程”重构为模板方法(校验 → 发验证码 → 创建账户 → 发欢迎邮件) |
| 📚 第二周 | 用模板方法实现“数据导入”流程(读取 → 解析 → 校验 → 保存 → 通知) |
| 📚 第三周 | 阅读 Spring 源码:JdbcTemplate.execute()、TransactionTemplate.execute() 如何实现? |
| 📚 第四周 | 在 Spring Boot 中,创建一个 DataImportTemplate,支持不同数据格式(CSV、Excel) |
| 📚 面试准备 | 准备回答: |
“你项目中哪里用了模板方法?”
“模板方法和策略模式区别?”
“Spring 中的 JdbcTemplate 是模板方法吗?”
“钩子方法有什么用?” |
✅ 八、模板方法模式面试高频题(附答案)
Q1:模板方法模式解决了什么问题?
A:解决了“多个子类有相同流程,但部分步骤不同”的问题,避免代码重复,强制流程规范。
Q2:模板方法模式和策略模式的区别?
A:
- 模板方法:继承 + 固定流程,子类实现步骤
- 策略模式:组合 + 固定接口,客户端选择算法
→ 一个是“你必须按我的流程做”,一个是“你可以选不同的流程”
Q3:Spring 中的 JdbcTemplate 是模板方法模式吗?
A:是的!
JdbcTemplate.execute()是模板方法,RowMapper.mapRow()是抽象方法,客户端只需实现映射逻辑。
Q4:什么是钩子方法?
A:钩子方法(Hook Method)是模板方法中有默认实现的 protected 方法,子类可选择性重写,用于扩展功能而不改变流程。
Q5:为什么模板方法要声明为 final?
A:防止子类重写整个流程,破坏算法结构,确保流程的完整性。
✅ 九、总结:模板方法模式选型决策树
graph TD
A[是否有多个子类共享相同流程?] --> B{流程固定,但部分步骤可变?}
B -->|是| C[使用模板方法模式]
B -->|否| D[使用策略模式或直接写方法]
✅ 最终推荐:
- 流程固定、步骤可变 → ✅ 模板方法模式(最标准)
- 算法可变、流程可换 → ✅ 策略模式
- 流程简单、无变化 → ❌ 不用模式,直接写方法
✅ 十、结语:真正的高手,不教别人怎么做,只告诉他们必须按这个顺序做
你不是在学“模板方法模式”,你是在学“如何为复杂流程建立标准化、可复用、可扩展的框架”。
✅ 当你看到
JdbcTemplate只让你写 SQL 和映射,连接、关闭、异常、事务全由它管理时,你已经掌握了 Spring 的灵魂。
✅ 当你用一个OrderProcessTemplate统一管理所有订单流程,新增“企业订单”只需一个类时,你已经是架构师了。
不要为了“用模式”而用模式,
要为了“系统可维护、可扩展、可规范”而选择它。
Java 模板方法模式深度学习指南&spm=1001.2101.3001.5002&articleId=152561776&d=1&t=3&u=9339f76b0f1341a29e44cc6de0c3395b)
660

被折叠的 条评论
为什么被折叠?



