Java案例深度解析与实战问答
目录导读
- 模板方法模式是什么?——定义与核心思想
- 为什么需要模板方法模式?——解决代码复用与扩展性痛点
- Java案例实战:咖啡与茶的制作流程
- 模板方法模式的结构解析
- 进阶技巧:钩子方法(Hook)的灵活运用
- 常见问题问答(FAQ)
- 总结与最佳实践
模板方法模式是什么?
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义一个操作中的算法骨架,而将某些步骤的实现延迟到子类中,通俗地说,父类定流程,子类定细节”。

核心思想:
- 在抽象类中定义一个模板方法(通常为 final,防止子类重写)。
- 模板方法中调用一系列基本方法(抽象方法或具体方法)。
- 子类通过实现抽象方法或重写钩子方法来定制具体行为。
一句话记忆:父类写剧本,子类演角色。
为什么需要模板方法模式?
痛点场景
假设你要实现两个功能类似的类:CoffeeMaker 和 TeaMaker,它们都有“烧水”、“冲泡”、“倒杯”、“加调料”四个步骤,但冲泡的原料和加调料的逻辑不同,如果直接复制代码,维护成本高,且违反 DRY 原则。
模式优势
- 代码复用:将公共算法步骤上移到父类,子类只关注差异部分。
- 扩展性:新增饮品(如牛奶)只需新写一个子类,无需修改现有代码。
- 控制反转:父类控制流程,子类提供实现,符合好莱坞原则(Don't call us, we'll call you)。
Java案例实战:咖啡与茶的制作流程
我们以一个经典的“饮品制作”案例来演示,假设咖啡和茶的制作步骤一致,但细节不同。
1 定义抽象类 Beverage
public abstract class Beverage {
// 模板方法,控制制作流程(固定步骤)
public final void prepareRecipe() {
boilWater(); // 步骤1:烧水
brew(); // 步骤2:冲泡(抽象方法)
pourInCup(); // 步骤3:倒杯(具体方法)
addCondiments(); // 步骤4:加调料(抽象方法)
}
// 具体方法:所有子类共享
public void boilWater() {
System.out.println("烧开水...");
}
public void pourInCup() {
System.out.println("将饮品倒入杯中...");
}
// 抽象方法:子类必须实现
public abstract void brew();
public abstract void addCondiments();
}
2 创建具体子类
咖啡类 Coffee:
public class Coffee extends Beverage {
@Override
public void brew() {
System.out.println("用沸水冲泡咖啡粉...");
}
@Override
public void addCondiments() {
System.out.println("加入糖和牛奶...");
}
}
茶类 Tea:
public class Tea extends Beverage {
@Override
public void brew() {
System.out.println("用沸水浸泡茶叶...");
}
@Override
public void addCondiments() {
System.out.println("加入柠檬...");
}
}
3 客户端测试
public class Client {
public static void main(String[] args) {
System.out.println("===== 制作咖啡 =====");
Beverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("\n===== 制作茶 =====");
Beverage tea = new Tea();
tea.prepareRecipe();
}
}
输出结果:
===== 制作咖啡 =====
烧开水...
用沸水冲泡咖啡粉...
将饮品倒入杯中...
加入糖和牛奶...
===== 制作茶 =====
烧开水...
用沸水浸泡茶叶...
将饮品倒入杯中...
加入柠檬...
模板方法模式的结构解析
类图结构(UML)
+----------------+ +----------------+
| AbstractClass | | ConcreteClass |
|----------------| |----------------|
| +templateMethod()| | +primitiveOp1()|
| +primitiveOp1() | | +primitiveOp2()|
| +primitiveOp2() | +----------------+
| +hookMethod() |
+----------------+
角色说明:
- 抽象类:定义模板方法,包含基本方法(抽象、具体、钩子)。
- 具体类:实现抽象方法和钩子方法,但不改变流程。
- 钩子方法:可选实现的“空方法”,子类可以重写以影响流程(如是否加调料)。
进阶技巧:钩子方法(Hook)的灵活运用
钩子方法允许子类条件性地控制模板方法的某一步骤,某些客户可能不希望加任何调料。
增加钩子方法
修改 Beverage 抽象类:
public abstract class Beverage {
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
// 钩子方法:判断是否加调料
if (customerWantsCondiments()) {
addCondiments();
}
}
// 默认钩子:返回 true
public boolean customerWantsCondiments() {
return true;
}
// 其余方法同前...
}
子类重写钩子
public class Coffee extends Beverage {
// 其他方法同前...
@Override
public boolean customerWantsCondiments() {
String answer = getUserInput(); // 模拟用户输入
return answer.equalsIgnoreCase("y");
}
private String getUserInput() {
// 简化实现,实际可读取控制台
return "n"; // 假设用户说“不需要”
}
}
现在运行 prepareRecipe(),如果用户输入“n”,则不会执行 addCondiments()。
常见问题问答(FAQ)
Q1:模板方法模式与策略模式有什么区别?
A:两者都封装了算法,但侧重点不同。
- 模板方法:通过继承在父类中定义算法骨架,子类重写部分步骤,适用于“算法结构固定,但步骤可变化”。
- 策略模式:通过组合将算法封装成独立对象,客户端可动态切换算法,适用于“算法整体可替换”。
Q2:模板方法是否必须是 final 的?
A:推荐用 final 修饰,防止子类无意中覆盖模板方法,破坏算法骨架,Java 官方也建议将模板方法声明为 final。
Q3:钩子方法是否必须实现?
A:不必,钩子方法在抽象类中提供默认实现(空方法或返回固定值),子类可以选择性重写,钩子方法的主要作用是让子类对模板方法的执行施加影响,而不必实现所有抽象方法。
Q4:模板方法模式在 JDK 中有哪些应用?
A:常见的有:
java.util.AbstractList:定义了addAll()、indexOf()等模板方法。java.io.InputStream:read()方法是模板方法,子类只需实现read(byte[], int, int)。javax.servlet.http.HttpServlet:doGet()、doPost()等是模板方法中的钩子,由service()调用。
Q5:如何避免模板方法导致类爆炸?
A:
- 合理设计抽象类,将共性尽可能上移。
- 结合工厂方法或策略模式,将变化点局部化。
- 使用组合而非继承(如策略模式)来替代部分模板方法的职责。
总结与最佳实践
适用场景
- 多个子类有相同的算法流程,但某些步骤的实现不同。
- 需要控制子类的扩展点,防止子类改变算法结构。
- 在框架设计中,通过模板方法提供“模板式”的扩展能力(如 Spring 的 JdbcTemplate)。
实践建议
- 抽象类中尽量多用 final 方法,尤其是模板方法本身。
- 钩子方法不要过多,否则会增加子类的复杂度。
- 将不变的部分上移,可变的部分下放——这符合开闭原则。
- 命名约定:模板方法通常以
template、execute、process开头,钩子方法以hook、before、after开头。
最后思考
模板方法模式通过“反向控制”实现了代码的高内聚与低耦合,是应对重复逻辑且流程固定的最优雅方案之一,如果你在做 Java 框架开发或处理批量业务规则时,不妨先问问自己:“这个流程的骨架是固定的吗?” 如果是,模板方法就是你的最佳伙伴。