什么是模板方法模式?应用场景有哪些?
2026年01月27日
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
面试考察点
面试官提出这个问题,通常旨在考察以下几个层面的能力:
- 对设计模式的认知广度与深度:确认你是否系统学习过常用的设计模式,而不仅仅是死记硬背概念。
- 对模板方法模式的本质理解:
- 不仅仅是知道定义,更想知道你是否理解其 “定义算法骨架,允许子类重写特定步骤” 的核心思想。
- 能否清晰区分 “模板方法” 与 “基本方法”(抽象方法、具体方法、钩子方法)。
- 实际应用与抽象思维能力:
- 能否识别出现有框架或项目中模板方法模式的应用,这体现了你的代码阅读经验和框架理解能力。
- 能否在合适的业务场景中主动应用该模式,解决 “代码重复” 和 “流程固化” 的问题。
- 对面向对象设计原则的把握:该模式是 “开闭原则”(对扩展开放,对修改封闭)和 “里氏替换原则” 的经典体现,面试官会间接考察你对这些原则的理解。
核心答案
模板方法模式 是一种行为设计模式。它在父类(抽象类)中定义一个操作中的算法骨架,而将一些步骤的具体实现延迟到子类中。模板方法使得子类可以在不改变算法整体结构的情况下,重新定义该算法的某些特定步骤。
核心就两点:1. 固定流程;2. 可变实现。
其典型的应用场景包括:
- 框架设计:各类框架(如 Spring, JUnit)中大量使用,用于固定核心流程,暴露扩展点给用户。
- 具有统一流程的业务逻辑:如订单处理(校验 -> 计价 -> 库存锁定 -> 持久化)、数据导出(准备数据 -> 生成文件 -> 上传到指定位置)等。
- 工具类与算法库:提供通用的算法框架,允许调用者定制部分逻辑。
深度解析
原理/机制
模板方法模式的结构非常清晰,主要包含两个角色:
- 抽象类(Abstract Class):
- 模板方法:一个
final方法,定义了算法的骨架和步骤执行顺序。它通常会调用下列各类 “基本方法”。 - 基本方法:
- 抽象方法:由子类必须实现的方法,对应算法中可变的步骤。
- 具体方法:在抽象类中已实现的方法,对应算法中固定的步骤,子类通常无需关心。
- 钩子方法:在抽象类中提供默认(通常为空)实现的方法。子类可以选择性地覆盖它,从而影响模板方法的流程。这是一种更细粒度的控制。
- 模板方法:一个
- 具体子类(Concrete Class):实现父类中定义的抽象方法,并可选择性地覆盖钩子方法。
该模式通过将不变的行为(流程)搬移到父类,去除子类中的重复代码,实现了良好的复用性,同时提供了足够的扩展灵活性。
代码示例
以“饮料冲泡”这个经典例子为例,泡咖啡和泡茶的流程固定,但某些步骤具体操作不同。
// 1. 抽象类:定义模板和基本方法
public abstract class BeverageTemplate {
// 模板方法:声明为 final,防止子类重写算法骨架
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
// 钩子方法控制是否添加调料
if (customerWantsCondiments()) {
addCondiments();
}
}
// 抽象方法:必须由子类实现
protected abstract void brew();
protected abstract void addCondiments();
// 具体方法:通用步骤,已实现
private void boilWater() {
System.out.println("将水煮沸");
}
private void pourInCup() {
System.out.println("倒入杯中");
}
// 钩子方法:子类可选择覆盖,提供默认实现
protected boolean customerWantsCondiments() {
return true; // 默认需要加调料
}
}
// 2. 具体子类:实现特定步骤
public class Coffee extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡粉");
}
@Override
protected void addCondiments() {
System.out.println("加入糖和牛奶");
}
// 覆盖钩子方法:提供个性化逻辑
@Override
protected boolean customerWantsCondiments() {
// 假设通过某种方式获取用户偏好,这里简化为固定返回 false
return false; // 这位客人喝黑咖啡
}
}
public class Tea extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("用沸水浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("加入柠檬");
}
}
// 3. 客户端使用
public class Client {
public static void main(String[] args) {
System.out.println("制作咖啡...");
BeverageTemplate coffee = new Coffee();
coffee.prepareRecipe(); // 执行固定流程,调用子类实现的特定步骤
System.out.println("\n制作茶...");
BeverageTemplate tea = new Tea();
tea.prepareRecipe();
}
}
对比分析与注意事项
-
与策略模式对比:两者都用于封装算法。关键区别在于:
- 模板方法模式 使用继承,通过子类来改变部分算法。它强调算法步骤的固定。
- 策略模式 使用组合,通过注入不同的策略对象来改变整个算法。它强调算法的完全可互换。
- 简单来说,模板方法是 “一部分变,一部分不变”;策略模式是 “整个都变,我提供几套完整方案给你选”。
最佳实践与常见误区
- 保护模板方法:模板方法 (
prepareRecipe) 应声明为final,防止子类重写从而破坏算法结构。 - 尽量减少抽象方法:不是所有步骤都需要是抽象的。将稳定的步骤实现为具体方法,减少子类的负担。
- 合理使用钩子方法:钩子方法提供了额外的控制点,但不宜过多,否则会提高系统复杂度,让流程变得难以理解。
- 警惕 “继承泛滥”:如果算法的可变部分过多,或者子类需要重写大量父类方法,可能意味着模板方法模式不适用,应考虑策略模式等基于组合的模式。
- 命名约定:良好的命名能提高代码可读性,如模板方法名可为
doWork(), 钩子方法名常以do...,will...,did...,is...,should...等开头。
总结
模板方法模式的核心价值在于 “流程复用与扩展” ,它通过将不变的算法骨架提升至父类来消除代码重复,并通过抽象方法和钩子方法为子类提供灵活的扩展点,是框架设计中构筑 “骨架” 的基石。