Java 模板方法模式从入门到实战(后端必看,附案例+面试考点)
前言:模板方法模式(Template Method Pattern)是Java设计模式中最易理解、最常用的"行为型模式"之一,核心是"定义固定流程模板,将可变步骤延迟到子类实现",无需修改模板即可灵活定制流程细节。
很多Java后端开发者在开发中会遇到这样的问题:面对多个相似的业务流程(如不同类型的订单处理、多种数据导出、各类接口调用流程),每个流程的核心步骤一致,但部分细节不同,习惯重复编写流程代码,导致代码冗余、维护成本高;面试时被问到"模板方法模式的核心是什么""模板方法与策略模式的区别""框架中哪里用到了模板方法",常常答不到重点。
本文从入门到实战,用极简语言拆解模板方法模式核心,结合可直接复制运行的入门案例、真实业务实战(Spring Boot环境),以及高频面试考点,带你吃透模板方法模式------新手能快速上手,中级开发者能落地项目,面试时能轻松应对追问,看完就能用在实际开发中。
一、为什么Java后端必须掌握模板方法模式?(痛点直击)
先看3个Java后端开发中最常见的场景,你一定遇到过,这也是模板方法模式的核心应用场景,更是面试中高频提及的"流程复用"典型场景:
-
场景1:电商订单处理流程,无论是普通订单、秒杀订单、预售订单,核心流程都是"校验订单→扣减库存→生成订单→通知用户",只有"校验订单"和"通知用户"的细节不同,若重复编写完整流程,代码冗余严重;
-
场景2:后台管理系统的数据导出功能,支持Excel、CSV、PDF三种格式,核心流程都是"查询数据→格式化数据→导出文件→记录日志",仅"格式化数据"和"导出文件"的逻辑不同,重复开发会增加维护成本;
-
场景3:第三方接口调用流程,调用微信、支付宝、银联的接口,核心流程都是"参数封装→签名验证→发送请求→结果解析",只有"签名验证"和"结果解析"的细节不同,重复编写流程会导致代码臃肿。
这些场景的共性问题:多个相似流程,核心步骤固定,仅部分细节不同,重复编写流程代码,导致冗余、可维护性差、扩展性弱。
而模板方法模式的核心价值,就是"流程复用+灵活定制"------将固定的核心流程定义为"模板",把可变的细节步骤抽象出来,让子类按需实现,无需修改模板即可扩展新的流程细节,完美符合"开闭原则"。
简单说,模板方法模式就是"定好流程框架,填不同的细节"。比如订单处理,先定义好4个核心步骤的模板,普通订单、秒杀订单只需各自实现"校验"和"通知"的细节,无需重复编写"扣减库存""生成订单"的代码,极大提升开发效率和代码可维护性。
核心结论:模板方法模式不是"花里胡哨"的设计,而是后端开发的"流程复用神器"------初级开发者用它减少代码冗余,中级开发者用它设计可扩展的业务流程,高级开发者用它理解框架底层设计(如Spring的生命周期、MyBatis的执行流程),面试时更是高频考点(中高级岗位必问)。
二、模板方法模式核心概念(极简入门,无需死记硬背)
模板方法模式的本质很简单:在抽象父类中定义一个"流程模板"(模板方法),将流程中的固定步骤封装为具体方法,可变步骤封装为抽象方法,子类继承父类后,实现抽象方法即可定制流程细节,无需修改模板方法本身。
就像做奶茶:核心流程(模板)是"煮茶→加配料→摇匀→装杯",其中"煮茶""摇匀""装杯"是固定步骤,"加配料"是可变步骤(加珍珠、加芋圆、加椰果);抽象父类定义这4个步骤的模板,子类(珍珠奶茶类、芋圆奶茶类)只需实现"加配料"的细节,即可完成不同奶茶的制作,无需重复编写其他固定步骤。
2.1 核心角色(2个核心,必记,区分角色是掌握模板方法模式的关键)
-
抽象模板角色(Abstract Template):定义流程模板,包含一个"模板方法"(定义固定流程的执行顺序)和两类方法------固定方法(核心步骤,父类实现,子类无需修改)、抽象方法(可变步骤,父类声明,子类必须实现);
-
具体模板角色(Concrete Template):继承抽象模板类,实现抽象方法,定制流程中的可变细节,无需修改模板方法和固定步骤。
核心原则:抽象模板定流程,具体模板填细节;固定步骤父类写,可变步骤子类改。这是模板方法模式的灵魂,也是它与其他行为型模式(如策略模式)的关键区别。
核心注意点:模板方法模式的核心是"固定流程+可变细节",解决的是"相似流程复用"的问题,与策略模式的"算法动态切换"、装饰器模式的"功能增强"有本质区别。
2.2 模板方法模式的核心流程(一句话看懂)
抽象父类定义模板方法(固定步骤顺序)→ 父类实现固定步骤,声明抽象的可变步骤 → 子类继承父类,实现抽象步骤 → 客户端调用子类对象的模板方法,自动执行完整流程(固定步骤+子类定制的可变步骤)。
三、模板方法模式入门实现(附可复制代码,新手必练)
以"奶茶制作流程"为入门案例,模拟3种奶茶(珍珠奶茶、芋圆奶茶、椰果奶茶),用模板方法模式实现"流程复用+细节定制",对比"普通重复实现"和"模板方法实现"的差异,一看就懂、一练就会。
3.1 普通实现:重复代码的反例(痛点凸显)
若不使用模板方法模式,每种奶茶都编写完整的制作流程,会导致大量重复代码,后续新增奶茶类型(如烧仙草),需重复编写固定步骤,维护成本极高。
java
// 珍珠奶茶类(完整流程,重复代码多)
public class PearlMilkTea {
// 制作流程(固定步骤+细节)
public void make() {
boilTea(); // 固定步骤:煮茶
addPearl(); // 可变细节:加珍珠
shake(); // 固定步骤:摇匀
cup(); // 固定步骤:装杯
}
// 固定步骤:煮茶
private void boilTea() {
System.out.println("煮制红茶基底");
}
// 可变细节:加珍珠
private void addPearl() {
System.out.println("加入黑珍珠");
}
// 固定步骤:摇匀
private void shake() {
System.out.println("摇匀奶茶");
}
// 固定步骤:装杯
private void cup() {
System.out.println("装入透明奶茶杯,完成\n");
}
}
// 芋圆奶茶类(重复固定步骤,冗余严重)
public class TaroBallMilkTea {
public void make() {
boilTea(); // 重复代码
addTaroBall(); // 可变细节:加芋圆
shake(); // 重复代码
cup(); // 重复代码
}
private void boilTea() {
System.out.println("煮制红茶基底");
}
private void addTaroBall() {
System.out.println("加入芋圆");
}
private void shake() {
System.out.println("摇匀奶茶");
}
private void cup() {
System.out.println("装入透明奶茶杯,完成\n");
}
}
// 客户端调用
public class Client {
public static void main(String[] args) {
PearlMilkTea pearl = new PearlMilkTea();
pearl.make();
TaroBallMilkTea taro = new TaroBallMilkTea();
taro.make();
// 痛点:新增椰果奶茶,需重复编写boilTea、shake、cup方法
// 代码冗余,维护成本高,修改固定步骤(如煮茶改为煮绿茶)需修改所有类
}
}
【运行结果】:
text
煮制红茶基底
加入黑珍珠
摇匀奶茶
装入透明奶茶杯,完成
煮制红茶基底
加入芋圆
摇匀奶茶
装入透明奶茶杯,完成
【缺点极其明显】:
-
代码冗余严重:固定步骤(煮茶、摇匀、装杯)在每个类中重复编写,增加代码量;
-
维护成本高:修改固定步骤(如煮茶改为煮绿茶),需修改所有子类,违背"开闭原则";
-
扩展性差:新增奶茶类型,需重复编写所有固定步骤,开发效率低;
-
可读性差:多个类的流程分散,难以快速梳理核心流程。
3.2 模板方法模式实现:流程复用的优雅代码(正例)
用模板方法模式重构奶茶制作流程,抽象父类定义流程模板和固定步骤,子类只需实现可变的"加配料"细节,无需重复编写固定步骤,新增奶茶类型只需新增子类,无需修改原有代码。
java
// 1. 抽象模板角色:奶茶制作模板
public abstract class MilkTeaTemplate {
// 模板方法:定义固定流程(核心,不可修改,子类直接调用)
public final void make() {
boilTea(); // 固定步骤1:煮茶
addIngredient(); // 可变步骤:加配料(抽象方法,子类实现)
shake(); // 固定步骤2:摇匀
cup(); // 固定步骤3:装杯
}
// 固定步骤1:煮茶(父类实现,子类无需修改)
protected void boilTea() {
System.out.println("煮制红茶基底");
}
// 可变步骤:加配料(抽象方法,子类必须实现,定制细节)
protected abstract void addIngredient();
// 固定步骤2:摇匀(父类实现,子类无需修改)
protected void shake() {
System.out.println("摇匀奶茶");
}
// 固定步骤3:装杯(父类实现,子类无需修改)
protected void cup() {
System.out.println("装入透明奶茶杯,完成\n");
}
}
// 2. 具体模板角色1:珍珠奶茶(实现可变步骤)
public class PearlMilkTea extends MilkTeaTemplate {
@Override
protected void addIngredient() {
System.out.println("加入黑珍珠"); // 珍珠奶茶的定制细节
}
}
// 2. 具体模板角色2:芋圆奶茶(实现可变步骤)
public class TaroBallMilkTea extends MilkTeaTemplate {
@Override
protected void addIngredient() {
System.out.println("加入芋圆"); // 芋圆奶茶的定制细节
}
}
// 2. 具体模板角色3:椰果奶茶(新增,无需修改原有代码)
public class CoconutMilkTea extends MilkTeaTemplate {
@Override
protected void addIngredient() {
System.out.println("加入椰果"); // 椰果奶茶的定制细节
}
}
// 3. 客户端调用(模板方法模式实现)
public class Client {
public static void main(String[] args) {
// 珍珠奶茶
MilkTeaTemplate pearl = new PearlMilkTea();
pearl.make();
// 芋圆奶茶
MilkTeaTemplate taro = new TaroBallMilkTea();
taro.make();
// 新增椰果奶茶,无需修改原有任何代码
MilkTeaTemplate coconut = new CoconutMilkTea();
coconut.make();
// 修改固定步骤(如煮茶改为煮绿茶),只需修改抽象父类的boilTea方法
}
}
【运行结果】:
text
煮制红茶基底
加入黑珍珠
摇匀奶茶
装入透明奶茶杯,完成
煮制红茶基底
加入芋圆
摇匀奶茶
装入透明奶茶杯,完成
煮制红茶基底
加入椰果
摇匀奶茶
装入透明奶茶杯,完成
【代码优势极其明显】:
-
复用性极强:固定步骤在父类中统一实现,子类无需重复编写,极大减少代码冗余;
-
扩展性极强:新增奶茶类型,只需新增子类实现抽象方法,无需修改原有代码,符合"开闭原则";
-
维护成本低:修改固定步骤,只需修改抽象父类的对应方法,所有子类自动复用修改后的逻辑;
-
流程清晰:抽象父类统一管理核心流程,子类仅关注自身的定制细节,可读性和可维护性大幅提升;
-
规范流程:模板方法固定了步骤顺序,避免子类随意修改流程逻辑,保证业务一致性。
【核心总结】:模板方法模式的核心不是"新增功能",而是"流程复用与规范"------通过抽象父类定义固定流程模板,将可变细节延迟到子类实现,既保证了流程的一致性,又实现了细节的灵活性,解决了相似流程的重复开发问题。
四、模板方法模式实战(真实业务场景,可直接复用)
结合Java后端最常见的"订单处理系统"场景,用模板方法模式实现"不同类型订单的流程复用与细节定制",贴合真实项目开发(Spring Boot环境),代码可直接复制到项目中使用,解决实际开发中的流程冗余痛点。
4.1 实战场景说明
场景:电商系统有3种核心订单类型(普通订单、秒杀订单、预售订单),核心处理流程一致,仅部分细节不同:
-
核心流程(固定):校验订单 → 扣减库存 → 生成订单 → 通知用户;
-
可变细节:
-
普通订单:校验订单(校验库存是否充足)、通知用户(短信通知);
-
秒杀订单:校验订单(校验秒杀资格、库存是否充足)、通知用户(短信+APP推送);
-
预售订单:校验订单(校验预售资格、支付定金)、通知用户(短信+公众号推送)。
-
要求:用模板方法模式实现订单处理逻辑,支持流程复用,新增订单类型(如团购订单)时,无需修改原有流程代码,只需新增具体模板子类,贴合Spring Boot实战规范,支持异常统一处理。
-
抽象模板:订单处理模板(定义核心流程,实现固定步骤,声明可变步骤);
-
具体模板:普通订单、秒杀订单、预售订单处理类(实现各自的可变细节);
-
实战亮点:结合Spring依赖注入、统一异常处理,实现订单处理的规范化和可扩展性,贴合真实项目开发。
4.2 实战代码实现(Spring Boot环境,可直接复用)
java
// 1. 公共实体:订单实体类
@Data
public class Order {
private Long orderId;
private String orderType; // 订单类型:normal(普通)、seckill(秒杀)、presale(预售)
private Long userId;
private Double amount;
private Integer stock; // 商品库存
private Boolean isQualified; // 是否具备购买资格(秒杀/预售专用)
private Boolean hasPaidDeposit; // 是否支付定金(预售专用)
}
// 2. 公共实体:响应结果封装
@Data
public class Result {
private Integer code; // 200成功,500失败
private String message;
private Object data;
public static Result success(String message, Object data) {
Result result = new Result();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
public static Result fail(String message) {
Result result = new Result();
result.setCode(500);
result.setMessage(message);
return result;
}
}
// 3. 抽象模板角色:订单处理模板
@Component
public abstract class OrderHandlerTemplate {
// 模板方法:核心订单处理流程(final修饰,防止子类修改流程顺序)
public final Result handleOrder(Order order) {
try {
// 固定步骤1:校验订单(可变细节,子类实现)
boolean validate = validateOrder(order);
if (!validate) {
return Result.fail("订单校验失败");
}
// 固定步骤2:扣减库存(父类实现,所有订单统一逻辑)
deductStock(order);
// 固定步骤3:生成订单(父类实现,所有订单统一逻辑)
Order generatedOrder = generateOrder(order);
// 固定步骤4:通知用户(可变细节,子类实现)
notifyUser(generatedOrder);
return Result.success("订单处理成功", generatedOrder);
} catch (Exception e) {
return Result.fail("订单处理失败:" + e.getMessage());
}
}
// 可变步骤1:校验订单(抽象方法,子类实现具体校验逻辑)
protected abstract boolean validateOrder(Order order);
// 固定步骤2:扣减库存(所有订单统一逻辑,父类实现)
protected void deductStock(Order order) {
if (order.getStock() <= 0) {
throw new RuntimeException("库存不足");
}
order.setStock(order.getStock() - 1);
System.out.println("库存扣减成功,剩余库存:" + order.getStock());
}
// 固定步骤3:生成订单(所有订单统一逻辑,父类实现,简化生成订单ID)
protected Order generateOrder(Order order) {
order.setOrderId(System.currentTimeMillis()); // 用时间戳作为订单ID
System.out.println("订单生成成功,订单ID:" + order.getOrderId());
return order;
}
// 可变步骤2:通知用户(抽象方法,子类实现具体通知逻辑)
protected abstract void notifyUser(Order order);
}
// 4. 具体模板角色1:普通订单处理类
@Component("normalOrderHandler")
public class NormalOrderHandler extends OrderHandlerTemplate {
@Override
protected boolean validateOrder(Order order) {
// 普通订单校验:仅校验库存
System.out.println("普通订单校验:校验库存");
return order.getStock() > 0;
}
@Override
protected void notifyUser(Order order) {
// 普通订单通知:短信通知
System.out.println("普通订单通知:向用户" + order.getUserId() + "发送短信,通知订单处理成功\n");
}
}
// 4. 具体模板角色2:秒杀订单处理类
@Component("seckillOrderHandler")
public class SeckillOrderHandler extends OrderHandlerTemplate {
@Override
protected boolean validateOrder(Order order) {
// 秒杀订单校验:校验库存 + 校验秒杀资格
System.out.println("秒杀订单校验:校验库存 + 校验秒杀资格");
return order.getStock() > 0 && order.getIsQualified();
}
@Override
protected void notifyUser(Order order) {
// 秒杀订单通知:短信 + APP推送
System.out.println("秒杀订单通知:向用户" + order.getUserId() + "发送短信和APP推送,通知订单处理成功\n");
}
}
// 4. 具体模板角色3:预售订单处理类
@Component("presaleOrderHandler")
public class PresaleOrderHandler extends OrderHandlerTemplate {
@Override
protected boolean validateOrder(Order order) {
// 预售订单校验:校验预售资格 + 校验定金支付 + 校验库存
System.out.println("预售订单校验:校验预售资格 + 校验定金支付 + 校验库存");
return order.getStock() > 0 && order.getIsQualified() && order.getHasPaidDeposit();
}
@Override
protected void notifyUser(Order order) {
// 预售订单通知:短信 + 公众号推送
System.out.println("预售订单通知:向用户" + order.getUserId() + "发送短信和公众号推送,通知订单处理成功\n");
}
}
// 5. 订单服务层:统一调度订单处理(Spring Bean)
@Service
public class OrderService {
// 注入所有订单处理模板(key:订单类型对应的bean名称,value:模板对象)
private final Map<String, OrderHandlerTemplate> orderHandlerMap;
// 构造方法注入(Spring自动将所有OrderHandlerTemplate实现类注入到map中)
public OrderService(Map<String, OrderHandlerTemplate> orderHandlerMap) {
this.orderHandlerMap = orderHandlerMap;
}
// 统一订单处理入口:根据订单类型选择对应的模板
public Result processOrder(Order order) {
// 根据订单类型获取对应的处理模板
String handlerBeanName = order.getOrderType() + "OrderHandler";
OrderHandlerTemplate handler = orderHandlerMap.get(handlerBeanName);
if (handler == null) {
return Result.fail("不支持该类型订单");
}
// 调用模板方法,执行订单处理流程
return handler.handleOrder(order);
}
}
// 6. 控制层:接口对外提供访问(Spring Boot Controller)
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/process")
public Result processOrder(@RequestBody Order order) {
return orderService.processOrder(order);
}
}
// 7. 测试类(模拟接口调用,Spring Boot环境可直接注入测试)
public class OrderTest {
public static void main(String[] args) {
// 模拟Spring容器注入
OrderHandlerTemplate normalHandler = new NormalOrderHandler();
OrderHandlerTemplate seckillHandler = new SeckillOrderHandler();
OrderHandlerTemplate presaleHandler = new PresaleOrderHandler();
// 初始化订单处理map
Map<String, OrderHandlerTemplate> handlerMap = new HashMap<>();
handlerMap.put("normalOrderHandler", normalHandler);
handlerMap.put("seckillOrderHandler", seckillHandler);
handlerMap.put("presaleOrderHandler", presaleHandler);
// 初始化服务
OrderService orderService = new OrderService(handlerMap);
// 测试普通订单
Order normalOrder = new Order();
normalOrder.setOrderType("normal");
normalOrder.setUserId(1001L);
normalOrder.setAmount(99.0);
normalOrder.setStock(10);
System.out.println(orderService.processOrder(normalOrder));
// 测试秒杀订单
Order seckillOrder = new Order();
seckillOrder.setOrderType("seckill");
seckillOrder.setUserId(1002L);
seckillOrder.setAmount(59.0);
seckillOrder.setStock(5);
seckillOrder.setIsQualified(true);
System.out.println(orderService.processOrder(seckillOrder));
// 测试预售订单
Order presaleOrder = new Order();
presaleOrder.setOrderType("presale");
presaleOrder.setUserId(1003L);
presaleOrder.setAmount(199.0);
presaleOrder.setStock(3);
presaleOrder.setIsQualified(true);
presaleOrder.setHasPaidDeposit(true);
System.out.println(orderService.processOrder(presaleOrder));
}
}
【运行结果】:
text
普通订单校验:校验库存
库存扣减成功,剩余库存:9
订单生成成功,订单ID:1713349260000
普通订单通知:向用户1001发送短信,通知订单处理成功
Result(code=200, message=订单处理成功, data=Order(orderId=1713349260000, orderType=normal, userId=1001, amount=99.0, stock=9, isQualified=null, hasPaidDeposit=null))
秒杀订单校验:校验库存 + 校验秒杀资格
库存扣减成功,剩余库存:4
订单生成成功,订单ID:1713349260001
秒杀订单通知:向用户1002发送短信和APP推送,通知订单处理成功
Result(code=200, message=订单处理成功, data=Order(orderId=1713349260001, orderType=seckill, userId=1002, amount=59.0, stock=4, isQualified=true, hasPaidDeposit=null))
预售订单校验:校验预售资格 + 校验定金支付 + 校验库存
库存扣减成功,剩余库存:2
订单生成成功,订单ID:1713349260002
预售订单通知:向用户1003发送短信和公众号推送,通知订单处理成功
Result(code=200, message=订单处理成功, data=Order(orderId=1713349260002, orderType=presale, userId=1003, amount=199.0, stock=2, isQualified=true, hasPaidDeposit=true))
【实战亮点】:
-
贴合Spring Boot实战:使用@Component、@Service、@RestController等注解,符合真实项目开发规范,可直接复制复用;
-
流程复用彻底:固定的订单处理步骤(扣减库存、生成订单)在抽象父类统一实现,子类仅关注自身的校验和通知细节;
-
扩展性极强:新增订单类型(如团购订单),只需新增子类继承OrderHandlerTemplate,实现抽象方法,无需修改原有服务和控制器代码;
-
规范统一:模板方法固定了订单处理的步骤顺序,避免子类随意修改流程,保证所有订单处理逻辑的一致性;
-
异常统一处理:模板方法中统一捕获异常,返回标准化响应,贴合接口开发的实际需求。
补充:真实项目中,可结合自定义注解(如@OrderType("normal")),简化订单类型与模板的映射,无需手动拼接bean名称,进一步提升开发效率。
五、模板方法模式在JDK/框架中的应用(面试必提)
模板方法模式的核心价值是"流程复用与规范",这也是它被广泛应用在JDK源码和主流Java框架中的原因,掌握这些应用场景,面试时能加分不少,还能帮助你理解框架底层设计思想。
5.1 JDK 中的模板方法模式(最常见,面试高频)
JDK中有多个经典的模板方法模式应用,其中AbstractList、InputStream是面试必问考点,一定要掌握:
5.1.1 AbstractList 抽象类(最典型)
Java中的AbstractList(抽象列表),是模板方法模式的标准实现,定义了列表操作的核心流程,将可变细节延迟到子类实现:
-
抽象模板(Abstract Template):AbstractList抽象类,定义了get(int index)、add(int index, E element)等模板方法,封装了列表操作的固定流程;
-
具体模板(Concrete Template):ArrayList、LinkedList等子类,实现了AbstractList中的抽象方法(如size()、get(int index)),定制列表的具体实现细节;
-
核心逻辑:AbstractList定义了列表操作的统一流程,子类只需实现抽象方法,即可完成列表的具体功能,无需重复编写流程代码。
java
// AbstractList中的模板方法示例(简化)
public abstract class AbstractList<E> implements List<E> {
// 模板方法:add方法,定义固定流程
public boolean add(E e) {
add(size(), e); // 调用另一个模板方法
return true;
}
// 模板方法:add(int index, E element),定义固定流程
public void add(int index, E element) {
throw new UnsupportedOperationException(); // 默认实现,子类可重写
}
// 抽象方法:size(),可变细节,子类必须实现
public abstract int size();
// 抽象方法:get(int index),可变细节,子类必须实现
public abstract E get(int index);
}
// 具体模板:ArrayList,实现抽象方法
public class ArrayList<E> extends AbstractList<E> {
private Object[] elementData;
private int size;
@Override
public int size() {
return size; // ArrayList的size实现
}
@Override
public E get(int index) {
rangeCheck(index); // 校验索引(固定逻辑)
return elementData(index); // 返回对应元素(ArrayList的具体实现)
}
@Override
public void add(int index, E element) {
// 实现add的具体逻辑(扩容、移位、赋值)
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
}
核心逻辑:AbstractList定义了列表操作的模板流程,ArrayList、LinkedList等子类通过实现抽象方法,定制自身的存储和操作细节,实现了流程复用,同时保证了列表操作的统一性。
5.1.2 InputStream 抽象类
Java中的InputStream(输入流),底层采用模板方法模式实现,定义了输入流读取的核心流程:
-
抽象模板(Abstract Template):InputStream抽象类,定义了read()、read(byte[] b)等模板方法,封装了读取数据的固定流程;
-
具体模板(Concrete Template):FileInputStream、ByteArrayInputStream等子类,实现了InputStream中的抽象方法(如read()),定制不同输入流的读取细节;
-
核心逻辑:InputStream定义了"读取数据"的统一流程,子类只需实现具体的读取逻辑,即可实现不同类型的输入流操作,无需重复编写流程代码。
5.2 框架中的模板方法模式
5.2.1 Spring 中的 AbstractApplicationContext
Spring框架的AbstractApplicationContext(抽象应用上下文),是模板方法模式的典型应用,定义了Spring容器初始化的核心流程:
-
抽象模板(Abstract Template):AbstractApplicationContext抽象类,定义了refresh()模板方法,封装了容器初始化的固定步骤(如准备刷新、获取BeanFactory、初始化BeanFactory等);
-
具体模板(Concrete Template):ClassPathXmlApplicationContext、AnnotationConfigApplicationContext等子类,实现了抽象方法,定制不同类型应用上下文的初始化细节;
-
核心逻辑:refresh()方法固定了Spring容器初始化的流程,子类只需实现抽象方法,即可完成不同类型容器的初始化,实现流程复用。
5.2.2 MyBatis 中的 BaseExecutor
MyBatis的BaseExecutor(基础执行器),底层采用模板方法模式实现,定义了SQL执行的核心流程:
-
抽象模板(Abstract Template):BaseExecutor抽象类,定义了query()、update()等模板方法,封装了SQL执行的固定流程(如获取连接、创建Statement、执行SQL、处理结果等);
-
具体模板(Concrete Template):SimpleExecutor、ReuseExecutor等子类,实现了抽象方法,定制不同执行器的SQL执行细节;
-
核心逻辑:BaseExecutor定义了SQL执行的统一流程,子类只需实现具体的执行逻辑,即可实现不同类型的SQL执行方式,提升代码复用性。
六、模板方法模式面试高频考点(必背,避坑)
模板方法模式是Java后端面试的高频考点(中高级岗位尤为突出),重点考察"核心思想""核心角色""JDK应用""与其他模式的区别",记住以下考点,轻松应对面试。
1. 模板方法模式的核心作用是什么?(高频)
核心答案(一句话记住,面试直接说):定义固定的业务流程模板,将流程中的可变细节延迟到子类实现,实现流程复用和规范,同时保证扩展性,符合开闭原则。
补充:模板方法模式解决的是"相似流程重复开发"的问题,核心是"流程复用与规范",而非"算法切换"或"功能增强"。
2. 模板方法模式的核心角色有哪些?(必背)
核心答案(一句话记住):抽象模板(定义流程模板和固定步骤,声明抽象方法)、具体模板(继承抽象模板,实现抽象方法,定制细节)。
| 角色名称 | 核心职责 | 示例 |
|---|---|---|
| 抽象模板 | 定义流程模板(模板方法),实现固定步骤,声明抽象的可变步骤,规范流程顺序 | MilkTeaTemplate、AbstractList |
| 具体模板 | 继承抽象模板,实现抽象方法,定制流程中的可变细节,不修改模板方法 | PearlMilkTea、ArrayList |
3. 模板方法模式和策略模式的区别?(高频,必背)
很多面试官会把这两个行为型模式放在一起问,核心区别(一句话区分):模板方法模式是"固定流程+可变细节",通过继承实现,流程不可变、细节可变;策略模式是"算法动态切换",通过组合实现,算法平等可完全替换。
| 对比维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 核心目的 | 复用固定流程,定制细节,规范流程顺序 | 动态切换不同算法,算法可完全替换 |
| 实现方式 | 继承(子类继承抽象模板,实现抽象方法) | 组合(上下文持有抽象策略引用,动态切换) |
| 流程/算法关系 | 流程固定,细节可变,细节依赖于流程 | 算法平等,无依赖关系,可自由切换 |
| 灵活性 | 灵活性低,流程不可修改,仅能定制细节 | 灵活性高,可动态切换算法,新增算法无需修改原有代码 |
| 使用场景 | 相似流程,核心步骤固定,细节不同(如订单处理、数据导出) | 多种相似算法,需动态切换(如支付方式、排序方式) |
4. 模板方法模式的优缺点是什么?(必背)
核心答案(简洁好记,面试直接说):
-
优点:流程复用性高(固定步骤统一实现)、流程规范(模板方法固定步骤顺序)、扩展性强(新增细节只需新增子类)、维护成本低(修改固定步骤只需修改父类);
-
缺点:灵活性有限(流程不可修改,仅能定制细节)、子类数量增多(每种细节对应一个子类)、继承关系耦合(子类依赖抽象父类)。
5. 如何解决模板方法模式"子类数量过多"的问题?(进阶考点)
核心答案(面试加分):可结合"工厂模式"或"注解+反射"优化,由工厂类统一管理具体模板子类的创建和获取,客户端无需直接创建子类对象,只需传入细节标识,由工厂返回对应子类;也可通过"钩子方法"减少子类数量,将部分可选细节用钩子方法实现,子类无需强制实现所有细节。
补充:钩子方法(Hook Method)是模板方法模式的扩展,在抽象模板中定义默认实现的方法,子类可根据需求重写,用于控制流程的执行逻辑(如判断某个步骤是否需要执行),减少子类的实现成本。
七、总结(新手必看)
模板方法模式的核心很简单:定好流程框架,把不变的步骤写在父类,把可变的细节留给子类,既保证流程统一,又能灵活定制,还能减少重复代码。
对于新手来说,掌握模板方法模式的关键是"区分抽象模板和具体模板",理解"模板方法固定流程、抽象方法定制细节"的思想------先学会用模板方法模式优化简单的相似流程(如奶茶制作、简单订单处理),再结合Spring Boot实战案例,理解框架中的模板方法应用;对于中高级开发者,重点掌握模板方法与策略模式的区别,以及框架底层的模板方法设计,面试时才能从容应对。
最后记住:模板方法模式不是"万能的",它只适用于"核心流程固定、细节可变"的场景,若流程不固定、需要频繁修改流程顺序,不适合使用(此时更适合策略模式)。合理使用模板方法模式,能让你的代码更简洁、更规范、更易维护,这也是后端开发者从"会写代码"到"会写好代码"的关键一步。