一、模板方法模式核心定义
模板方法模式是行为型设计模式的一种,核心目的是:
定义一个算法的骨架(固定流程),将算法中某些步骤的具体实现延迟到子类中;子类可以在不改变算法整体结构的前提下,重写算法中的特定步骤。
简单来说:把业务流程的 "固定部分" 封装在父类的模板方法中,"可变部分" 留给子类实现,既保证流程统一,又支持步骤定制。
核心解决的问题
- 复用固定流程代码 :将多个子类共有的固定流程(如订单下单、支付、发货)提取到父类,避免代码重复;
- 标准化业务流程 :强制子类遵循统一的流程骨架,防止流程混乱(如必须先校验参数,再执行业务,最后记录日志);
- 扩展可变步骤 :子类可自定义流程中的可变步骤,无需修改父类的核心流程;
- 控制流程执行 :父类可通过 "钩子方法" 控制是否执行某个步骤,或调整步骤执行顺序;
- 符合开闭原则 :新增业务场景只需新增子类,重写可变步骤,无需修改父类流程。
生活类比
- 场景 1 :泡茶 / 泡咖啡
- 固定流程(模板方法):烧开水 → 准备杯子 → 放茶叶 / 咖啡粉 → 冲开水 → 加调料(可选)→ 饮用;
- 可变步骤:放茶叶 / 咖啡粉(子类实现)、加调料(茶加冰糖,咖啡加牛奶 / 糖);
- 核心:父类定义 "冲泡饮品" 的固定流程,子类(泡茶 / 泡咖啡)只实现自己的特有步骤。
- 场景 2 :考试答题
- 固定流程:拿到试卷 → 填写姓名 / 考号 → 做选择题 → 做填空题 → 做简答题 → 交卷;
- 可变步骤:各题型的答题内容(不同考生实现不同);
- 核心:考试流程固定,答题内容可变。
- 场景 3 :电商订单处理
- 固定流程:校验订单 → 扣减库存 → 生成支付单 → 记录订单日志 → 返回结果;
- 可变步骤:扣减库存(普通订单 / 秒杀订单逻辑不同)、生成支付单(不同支付方式);
- 核心:订单处理流程统一,库存扣减 / 支付生成可定制。
标准角色
| 角色 | 职责 | 类比(泡茶 / 咖啡场景) | 代码定位 |
|---|---|---|---|
| 抽象模板(AbstractClass) | 定义算法骨架(模板方法),包含:1. 模板方法:固定流程(如makeDrink());2. 抽象方法:可变步骤(子类实现);3. 具体方法:固定步骤(父类实现);4. 钩子方法:可选步骤(子类可重写) |
饮品抽象类(Drink) | 抽象类(核心) |
| 具体模板(ConcreteClass) | 实现抽象模板中的抽象方法(可变步骤),可选重写钩子方法,不修改模板方法的流程 | 泡茶类(Tea)、泡咖啡类(Coffee) | 子类(业务实现) |
核心 UML 类图

二、泡茶 / 泡咖啡
以 "泡茶 / 泡咖啡" 为例,实现模板方法模式的核心逻辑 ------ 父类定义固定冲泡流程,子类实现各自的可变步骤,体现 "固定流程 + 可变步骤" 的核心思想。
1. 步骤 1:定义抽象模板(饮品抽象类)
/**
* 抽象模板:饮品抽象类(定义冲泡饮品的固定流程)
*/
public abstract class AbstractDrink {
/**
* 模板方法:冲泡饮品的核心流程(final修饰,防止子类重写流程)
*/
public final void makeDrink() {
// 固定步骤1:烧开水
boilWater();
// 固定步骤2:准备杯子
prepareCup();
// 可变步骤3:放原料(茶叶/咖啡粉)→ 子类实现
addMaterial();
// 固定步骤4:冲开水
pourWater();
// 钩子方法:是否加调料(可选步骤)
if (needCondiment()) {
// 可变步骤5:加调料 → 子类实现
addCondiment();
}
// 固定步骤6:饮用
drink();
}
// ---------------------- 固定步骤(父类实现) ----------------------
/**
* 固定步骤:烧开水
*/
private void boilWater() {
System.out.println("【固定步骤】烧开水(100℃)");
}
/**
* 固定步骤:准备杯子
*/
private void prepareCup() {
System.out.println("【固定步骤】准备干净的杯子");
}
/**
* 固定步骤:冲开水
*/
private void pourWater() {
System.out.println("【固定步骤】向杯子中冲入开水");
}
/**
* 固定步骤:饮用
*/
private void drink() {
System.out.println("【固定步骤】饮品制作完成,可以饮用");
}
// ---------------------- 可变步骤(子类实现) ----------------------
/**
* 抽象方法:添加原料(茶叶/咖啡粉)→ 子类实现
*/
protected abstract void addMaterial();
/**
* 抽象方法:添加调料(冰糖/牛奶)→ 子类实现
*/
protected abstract void addCondiment();
// ---------------------- 钩子方法(可选重写) ----------------------
/**
* 钩子方法:是否需要加调料(默认需要,子类可重写)
*/
protected boolean needCondiment() {
return true;
}
}
2. 步骤 2:实现具体模板(泡茶类)
/**
* 具体模板:泡茶类(实现可变步骤,重写钩子方法)
*/
public class Tea extends AbstractDrink {
@Override
protected void addMaterial() {
System.out.println("【可变步骤】向杯子中加入龙井茶叶");
}
@Override
protected void addCondiment() {
System.out.println("【可变步骤】向茶中加入冰糖");
}
// 可选:重写钩子方法(比如无糖茶不需要加调料)
// @Override
// protected boolean needCondiment() {
// return false;
// }
}
3. 步骤 3:实现具体模板(泡咖啡类)
/**
* 具体模板:泡咖啡类(实现可变步骤)
*/
public class Coffee extends AbstractDrink {
@Override
protected void addMaterial() {
System.out.println("【可变步骤】向杯子中加入蓝山咖啡粉");
}
@Override
protected void addCondiment() {
System.out.println("【可变步骤】向咖啡中加入牛奶和方糖");
}
}
4. 客户端(测试冲泡饮品)
/**
* 客户端:测试模板方法模式(泡茶/泡咖啡)
*/
public class TemplateClient {
public static void main(String[] args) {
// 1. 泡茶
System.out.println("======= 制作茶 =======");
AbstractDrink tea = new Tea();
tea.makeDrink();
// 2. 泡咖啡
System.out.println("\n======= 制作咖啡 =======");
AbstractDrink coffee = new Coffee();
coffee.makeDrink();
}
}
输出结果
======= 制作茶 =======
【固定步骤】烧开水(100℃)
【固定步骤】准备干净的杯子
【可变步骤】向杯子中加入龙井茶叶
【固定步骤】向杯子中冲入开水
【可变步骤】向茶中加入冰糖
【固定步骤】饮品制作完成,可以饮用
======= 制作咖啡 =======
【固定步骤】烧开水(100℃)
【固定步骤】准备干净的杯子
【可变步骤】向杯子中加入蓝山咖啡粉
【固定步骤】向杯子中冲入开水
【可变步骤】向咖啡中加入牛奶和方糖
【固定步骤】饮品制作完成,可以饮用
三、Spring 实战版(订单处理流程)
在业务开发中,模板方法模式最核心的实战场景是订单处理流程标准化------ 父类定义 "订单创建→参数校验→库存扣减→支付生成→日志记录" 的固定流程,子类(普通订单 / 秒杀订单)只实现各自的库存扣减逻辑,既保证流程统一,又支持业务定制。
1. 依赖准备(Spring Boot)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 核心模型定义
import lombok.Data;
/**
* 订单DTO
*/
@Data
public class OrderDTO {
private String orderId; // 订单ID
private String productId; // 商品ID
private int quantity; // 购买数量
private double amount; // 订单金额
private String orderType; // 订单类型(NORMAL/SECKILL)
}
/**
* 订单处理结果
*/
@Data
@lombok.AllArgsConstructor
public class OrderResult {
private boolean success; // 是否成功
private String message; // 结果信息
private OrderDTO order; // 订单信息
}
3. 抽象模板(订单处理抽象类)
import lombok.extern.slf4j.Slf4j;
/**
* 抽象模板:订单处理抽象类(定义订单处理的固定流程)
*/
@Slf4j
public abstract class AbstractOrderHandler {
/**
* 模板方法:订单处理核心流程(final修饰,防止子类修改流程)
*/
public final OrderResult handleOrder(OrderDTO order) {
try {
// 固定步骤1:参数校验
validateParams(order);
// 固定步骤2:记录订单开始日志
logStart(order);
// 可变步骤3:扣减库存(子类实现)
deductStock(order);
// 固定步骤4:生成支付单
createPayOrder(order);
// 钩子方法:是否发送通知(可选)
if (needSendNotify()) {
// 可变步骤5:发送通知(子类实现)
sendNotify(order);
}
// 固定步骤6:记录订单完成日志
logComplete(order);
return new OrderResult(true, "订单处理成功", order);
} catch (Exception e) {
log.error("订单处理失败 | 订单ID:{},原因:{}", order.getOrderId(), e.getMessage());
return new OrderResult(false, "订单处理失败:" + e.getMessage(), order);
}
}
// ---------------------- 固定步骤(父类实现) ----------------------
/**
* 固定步骤:参数校验
*/
private void validateParams(OrderDTO order) {
log.info("【固定步骤】校验订单参数 | 订单ID:{}", order.getOrderId());
if (order.getProductId() == null || order.getProductId().isEmpty()) {
throw new IllegalArgumentException("商品ID不能为空");
}
if (order.getQuantity() <= 0) {
throw new IllegalArgumentException("购买数量必须大于0");
}
log.info("【固定步骤】参数校验通过 | 订单ID:{}", order.getOrderId());
}
/**
* 固定步骤:记录开始日志
*/
private void logStart(OrderDTO order) {
log.info("【固定步骤】订单处理开始 | 订单ID:{},类型:{}", order.getOrderId(), order.getOrderType());
}
/**
* 固定步骤:生成支付单
*/
private void createPayOrder(OrderDTO order) {
log.info("【固定步骤】生成支付单 | 订单ID:{},金额:{}元", order.getOrderId(), order.getAmount());
// 模拟生成支付单逻辑
}
/**
* 固定步骤:记录完成日志
*/
private void logComplete(OrderDTO order) {
log.info("【固定步骤】订单处理完成 | 订单ID:{}", order.getOrderId());
}
// ---------------------- 可变步骤(子类实现) ----------------------
/**
* 抽象方法:扣减库存(普通订单/秒杀订单逻辑不同)
*/
protected abstract void deductStock(OrderDTO order);
/**
* 抽象方法:发送通知(短信/推送,子类实现)
*/
protected abstract void sendNotify(OrderDTO order);
// ---------------------- 钩子方法(可选重写) ----------------------
/**
* 钩子方法:是否需要发送通知(默认需要)
*/
protected boolean needSendNotify() {
return true;
}
}
4. 具体模板(普通订单处理器)
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 具体模板:普通订单处理器(实现可变步骤)
*/
@Slf4j
@Component
public class NormalOrderHandler extends AbstractOrderHandler {
@Override
protected void deductStock(OrderDTO order) {
log.info("【可变步骤-普通订单】扣减普通库存 | 订单ID:{},商品ID:{},数量:{}",
order.getOrderId(), order.getProductId(), order.getQuantity());
// 模拟普通库存扣减逻辑(直接扣减)
}
@Override
protected void sendNotify(OrderDTO order) {
log.info("【可变步骤-普通订单】发送短信通知 | 订单ID:{}", order.getOrderId());
// 模拟发送短信逻辑
}
}
5. 具体模板(秒杀订单处理器)
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 具体模板:秒杀订单处理器(实现可变步骤,重写钩子方法)
*/
@Slf4j
@Component
public class SeckillOrderHandler extends AbstractOrderHandler {
@Override
protected void deductStock(OrderDTO order) {
log.info("【可变步骤-秒杀订单】扣减秒杀库存(加分布式锁) | 订单ID:{},商品ID:{},数量:{}",
order.getOrderId(), order.getProductId(), order.getQuantity());
// 模拟秒杀库存扣减逻辑(加锁、校验库存上限)
if (order.getQuantity() > 1) {
throw new IllegalArgumentException("秒杀订单限购1件");
}
}
@Override
protected void sendNotify(OrderDTO order) {
log.info("【可变步骤-秒杀订单】发送APP推送通知 | 订单ID:{}", order.getOrderId());
// 模拟发送APP推送逻辑
}
// 重写钩子方法:秒杀订单强制发送通知(也可改为false关闭)
@Override
protected boolean needSendNotify() {
return true;
}
}
6. 订单服务(业务入口)
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 订单服务(根据订单类型选择处理器)
*/
@Slf4j
@Service
public class OrderService {
// 处理器映射:订单类型 → 处理器
private final Map<String, AbstractOrderHandler> handlerMap = new HashMap<>();
/**
* 注入所有订单处理器(Spring自动扫描)
*/
@Resource
public OrderService(NormalOrderHandler normalOrderHandler, SeckillOrderHandler seckillOrderHandler) {
handlerMap.put("NORMAL", normalOrderHandler);
handlerMap.put("SECKILL", seckillOrderHandler);
}
/**
* 处理订单(根据类型选择处理器)
*/
public OrderResult processOrder(OrderDTO order) {
log.info("【订单服务】开始处理订单 | 订单ID:{},类型:{}", order.getOrderId(), order.getOrderType());
AbstractOrderHandler handler = handlerMap.get(order.getOrderType());
if (handler == null) {
return new OrderResult(false, "不支持的订单类型:" + order.getOrderType(), order);
}
return handler.handleOrder(order);
}
}
7. 客户端(Spring Boot 测试)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 客户端:测试Spring版模板方法模式
*/
@SpringBootApplication
public class SpringTemplateDemoApplication {
public static void main(String[] args) {
// 1. 启动Spring容器
ConfigurableApplicationContext context = SpringApplication.run(SpringTemplateDemoApplication.class, args);
OrderService orderService = context.getBean(OrderService.class);
// 2. 测试1:处理普通订单
System.out.println("======= 测试1:处理普通订单 =======");
OrderDTO normalOrder = new OrderDTO();
normalOrder.setOrderId("O20240317001");
normalOrder.setProductId("P1001");
normalOrder.setQuantity(2);
normalOrder.setAmount(199.8);
normalOrder.setOrderType("NORMAL");
OrderResult normalResult = orderService.processOrder(normalOrder);
System.out.println("普通订单处理结果:" + normalResult);
// 3. 测试2:处理秒杀订单
System.out.println("\n======= 测试2:处理秒杀订单 =======");
OrderDTO seckillOrder = new OrderDTO();
seckillOrder.setOrderId("O20240317002");
seckillOrder.setProductId("P2001");
seckillOrder.setQuantity(1); // 秒杀限购1件
seckillOrder.setAmount(99.9);
seckillOrder.setOrderType("SECKILL");
OrderResult seckillResult = orderService.processOrder(seckillOrder);
System.out.println("秒杀订单处理结果:" + seckillResult);
// 4. 测试3:处理非法秒杀订单(数量>1)
System.out.println("\n======= 测试3:处理非法秒杀订单 =======");
OrderDTO invalidSeckillOrder = new OrderDTO();
invalidSeckillOrder.setOrderId("O20240317003");
invalidSeckillOrder.setProductId("P2001");
invalidSeckillOrder.setQuantity(2); // 秒杀限购1件,非法
invalidSeckillOrder.setAmount(199.8);
invalidSeckillOrder.setOrderType("SECKILL");
OrderResult invalidResult = orderService.processOrder(invalidSeckillOrder);
System.out.println("非法秒杀订单处理结果:" + invalidResult);
context.close();
}
}
输出结果
======= 测试1:处理普通订单 =======
【订单服务】开始处理订单 | 订单ID:O20240317001,类型:NORMAL
【固定步骤】校验订单参数 | 订单ID:O20240317001
【固定步骤】参数校验通过 | 订单ID:O20240317001
【固定步骤】订单处理开始 | 订单ID:O20240317001,类型:NORMAL
【可变步骤-普通订单】扣减普通库存 | 订单ID:O20240317001,商品ID:P1001,数量:2
【固定步骤】生成支付单 | 订单ID:O20240317001,金额:199.8元
【可变步骤-普通订单】发送短信通知 | 订单ID:O20240317001
【固定步骤】订单处理完成 | 订单ID:O20240317001
普通订单处理结果:OrderResult(success=true, message=订单处理成功, order=OrderDTO(orderId=O20240317001, productId=P1001, quantity=2, amount=199.8, orderType=NORMAL))
======= 测试2:处理秒杀订单 =======
【订单服务】开始处理订单 | 订单ID:O20240317002,类型:SECKILL
【固定步骤】校验订单参数 | 订单ID:O20240317002
【固定步骤】参数校验通过 | 订单ID:O20240317002
【固定步骤】订单处理开始 | 订单ID:O20240317002,类型:SECKILL
【可变步骤-秒杀订单】扣减秒杀库存(加分布式锁) | 订单ID:O20240317002,商品ID:P2001,数量:1
【固定步骤】生成支付单 | 订单ID:O20240317002,金额:99.9元
【可变步骤-秒杀订单】发送APP推送通知 | 订单ID:O20240317002
【固定步骤】订单处理完成 | 订单ID:O20240317002
秒杀订单处理结果:OrderResult(success=true, message=订单处理成功, order=OrderDTO(orderId=O20240317002, productId=P2001, quantity=1, amount=99.9, orderType=SECKILL))
======= 测试3:处理非法秒杀订单 =======
【订单服务】开始处理订单 | 订单ID:O20240317003,类型:SECKILL
【固定步骤】校验订单参数 | 订单ID:O20240317003
【固定步骤】参数校验通过 | 订单ID:O20240317003
【固定步骤】订单处理开始 | 订单ID:O20240317003,类型:SECKILL
【可变步骤-秒杀订单】扣减秒杀库存(加分布式锁) | 订单ID:O20240317003,商品ID:P2001,数量:2
订单处理失败 | 订单ID:O20240317003,原因:秒杀订单限购1件
非法秒杀订单处理结果:OrderResult(success=false, message=订单处理失败:秒杀订单限购1件, order=OrderDTO(orderId=O20240317003, productId=P2001, quantity=2, amount=199.8, orderType=SECKILL))
四、模板方法模式的核心特点与适用场景
优点
- 复用固定流程代码:将子类共有的固定流程提取到父类,避免代码重复(如日志记录、参数校验);
- 标准化业务流程:强制子类遵循统一的流程骨架,防止流程混乱(如必须先校验再执行业务);
- 扩展灵活:子类只需实现可变步骤,无需修改父类核心流程,符合开闭原则;
- 控制流程执行 :通过
final修饰模板方法,防止子类篡改流程;通过钩子方法控制可选步骤;- 职责清晰:父类负责流程骨架,子类负责具体实现,符合单一职责原则;
- 便于维护:流程变更只需修改父类,所有子类自动生效。
缺点
- 类数量增加:每个可变流程对应一个子类,流程较多时类数量膨胀;
- 流程僵化:父类固定流程难以灵活调整,如需修改流程顺序,需修改父类(违反开闭原则);
- 学习成本:新手需理解父类流程和子类实现的关系,增加认知成本;
- 继承的局限性:模板方法基于继承实现,子类只能继承一个父类,限制了多流程复用。
适用场景
- 有固定流程的业务:如订单处理、支付流程、审批流程、数据导入导出;
- 需要复用固定代码的场景:如日志记录、参数校验、权限控制等通用步骤;
- 需要标准化流程的场景:如接口调用(请求→校验→调用→响应→日志);
- 框架扩展 :如 Spring 的
JdbcTemplate、RestTemplate(定义模板方法,用户实现回调);- 算法骨架固定的场景:如排序算法(固定比较 - 交换流程,可变比较规则)、加密算法(固定加解密流程,可变密钥生成)。
五、JDK/ Spring 中的原生应用(必须知道)
模板方法模式是框架设计的核心模式之一,以下是常见的原生实现:
1. JDK 核心模板方法
- java.io.InputStream/OutputStream :read()/write()是抽象方法(可变步骤),父类定义了流操作的固定流程;
- java.util.AbstractList/AbstractSet/AbstractMap :父类定义集合操作的固定流程(如add()/remove()的校验逻辑),子类实现具体存储逻辑;
- java.lang.Thread :run()是抽象方法(可变步骤),start()是模板方法(固定启动流程);
- java.util.Calendar :父类定义日期计算的固定流程,子类(GregorianCalendar)实现具体日期算法。
2. Spring 核心模板方法
- org.springframework.jdbc.core.JdbcTemplate :定义 JDBC 操作的固定流程(连接→执行→关闭→处理结果),用户通过RowMapper实现结果映射(可变步骤);
- org.springframework.web.client.RestTemplate :定义 HTTP 请求的固定流程(请求构建→发送→响应处理),用户实现请求参数 / 响应解析(可变步骤);
- org.springframework.transaction.support.TransactionTemplate :定义事务操作的固定流程(开启→执行→提交 / 回滚),用户实现业务逻辑(可变步骤);
- Spring MVC的HandlerAdapter :定义请求处理的固定流程(拦截→处理→视图解析),用户实现Controller(可变步骤)。
3. MyBatis 核心模板方法
- org.apache.ibatis.executor.BaseExecutor :定义 SQL 执行的固定流程(缓存→参数处理→执行→结果映射),子类(SimpleExecutor/BatchExecutor)实现具体执行逻辑;
- org.apache.ibatis.session.defaults.DefaultSqlSession :定义会话操作的固定流程,子类实现具体 CRUD 逻辑。
六、模板方法模式 vs 策略模式(易混淆点)
模板方法和策略模式都用于封装可变逻辑,但核心意图完全不同:
| 维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 核心目的 | 固定流程 + 可变步骤(流程统一,步骤定制) | 可变算法 + 统一接口(算法替换,接口统一) |
| 实现方式 | 继承(子类重写父类抽象方法) | 组合(上下文持有策略对象) |
| 流程控制 | 父类控制流程,子类无法修改 | 客户端控制策略选择,上下文不控制流程 |
| 复用维度 | 复用流程骨架 | 复用算法实现 |
| 扩展方式 | 新增子类实现可变步骤 | 新增策略类实现新算法 |
| 典型场景 | 订单处理、JDBC 操作、框架模板 | 支付方式、排序算法、校验规则 |
总结
- 模板方法模式的核心是定义算法 / 业务的固定流程骨架,将可变步骤延迟到子类实现 ,既保证流程统一,又支持步骤定制;
- 核心角色包括抽象模板(定义流程 + 固定步骤 + 抽象方法 + 钩子方法)、具体模板(实现可变步骤);
- 模板方法需用final修饰,防止子类篡改流程;钩子方法用于控制可选步骤的执行;
- 模板方法模式适用于有固定流程、需要复用通用代码、标准化业务流程的场景(如订单处理、框架模板);
- 模板方法基于继承实现,策略模式基于组合实现,二者互补:模板方法固定流程,策略模式替换算法。
