一、【概念定义】—— “定义算法骨架,延迟细节实现”
模板方法模式(Template Method Pattern):
一种“算法封装型”行为设计模式,它在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重定义算法的某些特定步骤。
核心思想:父类定义流程(模板方法),子类实现具体步骤(钩子方法)。
目标:复用算法结构,允许子类定制细节,符合好莱坞原则(“别调用我们,我们会调用你”)。
口诀:“流程骨架父类定,具体步骤子类填;算法结构不改变,扩展定制超简单!”
二、【解决的问题】—— “复制粘贴地狱”的三大灾难
想象你正在开发一个饮料冲泡系统:
- 复制粘贴:
Coffee.brew() 和 Tea.brew() 中都有“烧水”、“冲泡”、“倒杯”… 代码重复!
→ 修改“烧水温度”?每个类都要改!易遗漏! - 模板方法模式:
- Beverage 父类定义 prepareRecipe()(模板方法);
- boilWater(), pourInCup() → 父类实现(通用步骤);
- brew(), addCondiments() → 子类实现(定制步骤);
→ 流程统一,细节定制,修改只需改父类!
核心解决:
代码重复 → 算法骨架在父类复用;扩展困难 → 新增饮料?只需实现抽象方法;违反开闭原则 → 算法结构封闭,步骤实现开放!
三、【适用场景】—— “固定流程+可变细节”的四大名器
你该用模板方法模式,如果符合以下“四定原则”:
场景 | 描述 | 举例 |
算法结构固定 | 流程步骤顺序不变,但某些步骤实现不同 | 冲泡饮料、数据处理流程、游戏关卡流程 |
代码复用需求 | 多个类有相同算法骨架,避免重复 | 文档生成器、报表导出器、安装向导 |
控制子类扩展 | 父类控制流程,子类只能扩展特定步骤 | 框架基类(如Spring的JdbcTemplate)、测试用例基类 |
防止子类破坏流程 | 关键步骤(如事务提交)必须执行,不可跳过 | 数据库操作、文件处理、网络请求 |
警告:如果算法步骤完全自由,或无通用骨架 —— 模板方法不适用!
四、【经典案例】—— 四大“流程掌控者”的成名战
1. 【饮料冲泡系统】—— 咖啡、茶、奶茶,一套流程!
需要冲泡多种饮料,流程相似但细节不同
每个饮料类写完整流程 → 代码重复,维护困难;
模板方法:Beverage.prepareRecipe():烧水 → 冲泡 → 倒杯 → 加料;Coffee.brew() → 磨咖啡豆;Coffee.addCondiments() → 加糖加奶;Tea.brew() → 泡茶叶;Tea.addCondiments() → 加柠檬;
效果:新增“奶茶”?只需实现 brew() 和 addCondiments()!流程零修改!
2. 【数据处理流水线】—— 清洗→转换→存储,步骤可定制!
处理CSV、JSON、XML数据,流程:读取→清洗→转换→存储
每种数据格式写一套 → 读取和存储代码重复;
模板方法:DataProcessor.process():read() → clean() → transform() → save();CSVProcessor.read() → 读CSV;JSONProcessor.transform() → JSON转对象;
效果:新增“XML处理器”?只需实现四个方法!流程结构复用!
3. 【游戏关卡流程】—— 开始→游戏→结束,事件可定制!
每个关卡:初始化→主循环→清理→显示结算
每个关卡类写完整循环 → 初始化和清理代码重复;
模板方法:GameLevel.run():init() → while(running) { update(); render(); } → cleanup() → showResult();BossLevel.init() → 生成Boss;PuzzleLevel.update() → 解谜逻辑;
效果:新增“生存关卡”?只需重写关键方法!游戏循环结构不变!
4. 【文档生成器】—— 标题→内容→页脚,格式可定制!
生成PDF、HTML、Word文档,流程:写标题→写内容→写页脚
每种格式写一套 → 标题和页脚逻辑重复;
模板方法:
DocumentGenerator.generate():writeHeader() → writeBody() → writeFooter();PDFGenerator.writeHeader() → 写PDF标题;HTMLGenerator.writeBody() → HTML标签;
效果:新增“Markdown生成器”?只需实现三个方法!生成流程统一!
五、【结构简述】—— 模板宇宙的“骨架三剑客”
模板方法三要素:
角色 | 职责 | 比喻 |
AbstractClass(抽象类) | 定义模板方法(算法骨架)和抽象/钩子方法 | “菜谱大师” —— 规定“先烧水再冲泡”,但“加什么料”你定 |
ConcreteClass(具体子类) | 实现抽象方法,可选重写钩子方法 | “咖啡师/茶艺师” —— 实现“磨豆”或“泡茶” |
Template Method(模板方法) | 定义算法流程,调用抽象/钩子方法 | “标准流程” —— prepareRecipe() |
调用流程:
Client → 调用 concrete.prepareRecipe() → 父类模板方法 → 调用 brew()(子类实现)→ 调用 addCondiments()(子类实现)
关键:模板方法在父类中定义且不应被子类重写(通常 final)!
六、【注意事项】—— “骨架”虽强,小心“僵化设计”!
1. 流程僵化
算法步骤顺序固定 → 无法跳过或重排步骤!
解决:用钩子方法(Hook) 提供默认空实现,子类可选重写;如:hookBeforeBrew() → 子类可决定是否执行前置操作。
2. 继承束缚
强制使用继承 → 子类必须实现所有抽象方法!
解决:用策略模式+组合代替(更灵活,但失去流程控制);或提供默认实现(Java 8+ 的 default 方法)。
3. 过度设计
简单流程也用模板方法 → 增加无谓复杂度!
权衡:流程复用需求 > 类数量成本 时使用。
4. 模板方法 vs 策略模式
模板方法:继承,父类控制流程,子类填空;策略模式:组合,客户端自由替换算法;
选择:需控制流程、复用骨架 → 模板方法;需运行时切换算法 → 策略模式。
七、【实战应用】—— 35行代码,打造“饮料冲泡模板系统”
java
深色版本
// 1. 抽象类(定义骨架)
abstract class Beverage {
// 模板方法:定义算法骨架(final 防止子类重写)
public final void prepareRecipe() {
System.out.println("\n=== 开始冲泡饮料 ===");
boilWater(); // 通用步骤(父类实现)
brew(); // 抽象步骤(子类实现)
pourInCup(); // 通用步骤(父类实现)
addCondiments(); // 抽象步骤(子类实现)
System.out.println("=== 请享用! ===\n");
}
// 通用步骤:父类直接实现
void boilWater() {
System.out.println(" 烧水至95°C");
}
void pourInCup() {
System.out.println(" 倒入杯中");
}
// 抽象步骤:子类必须实现
abstract void brew();
abstract void addCondiments();
// 钩子方法(Hook):子类可选重写(默认不执行)
boolean customerWantsCondiments() {
return true; // 默认加料
}
}
// 2. 具体子类:咖啡
class Coffee extends Beverage {
@Override
void brew() {
System.out.println(" 研磨咖啡豆并冲泡");
}
@Override
void addCondiments() {
if (customerWantsCondiments()) {
System.out.println(" 加入牛奶和糖");
} else {
System.out.println(" 顾客不要加料");
}
}
// 重写钩子方法
@Override
boolean customerWantsCondiments() {
return false; // 咖啡默认不加料(示例)
}
}
// 3. 具体子类:茶
class Tea extends Beverage {
@Override
void brew() {
System.out.println(" 用热水浸泡茶叶");
}
@Override
void addCondiments() {
if (customerWantsCondiments()) {
System.out.println(" 加入柠檬片和蜂蜜");
}
}
// 使用默认钩子(customerWantsCondiments() = true)
}
// 4. 客户端
public class TemplateMethodDemo {
public static void main(String[] args) {
System.out.println(" 饮料冲泡系统启动...");
Beverage coffee = new Coffee();
Beverage tea = new Tea();
coffee.prepareRecipe(); // 咖啡流程
tea.prepareRecipe(); // 茶流程
// 新增饮料?只需继承 Beverage 并实现 brew() 和 addCondiments()!
// 流程结构无需修改!
}
}
运行结果:
深色版本
饮料冲泡系统启动...
=== 开始冲泡饮料 ===
烧水至95°C
研磨咖啡豆并冲泡
倒入杯中
顾客不要加料
=== 请享用! ===
=== 开始冲泡饮料 ===
烧水至95°C
用热水浸泡茶叶
倒入杯中
加入柠檬片和蜂蜜
=== 请享用! ===
看到了吗?父类 Beverage 完全控制流程!子类只需专注实现 brew() 和 addCondiments()!
新增“奶茶”?只需:
class MilkTea extends Beverage { void brew() { System.out.println(" 冲泡红茶+加入奶精"); } void addCondiments() { System.out.println(" 加入珍珠和糖浆"); } }
—— 流程复用,扩展无忧!这就是模板方法的力量!
最后总结:模板方法模式 —— 对象界的“流程架构师”
以前:复制粘贴 → 代码重复,修改易错;
现在:模板骨架 → 父类定流程,子类填细节!
模板方法模式就是对象界的“标准化大师” —— 用一个不可变的骨架,容纳无限变化的细节,让扩展在框架内优雅生长!
记忆口诀:
算法骨架父类掌,
具体步骤子类扛。
流程固定不改变,
扩展定制它最强!
冲泡饮料数据流,
游戏文档全在行。
钩子方法留后路,
继承虽紧威力扬!
莫把简单流程绑,
复用需求再登场!