模板方法模式:框架中的算法骨架
FreeGuideOnline
最新
2026-06-18
模板方法模式:框架中的算法骨架
你是否曾想过,为什么很多框架都要求你按照特定的步骤去重写某些方法?它们如何在“不变”中容纳“万变”?这就是模板方法模式的精髓。
什么是模板方法模式?
模板方法模式是一种行为设计模式,它在父类中定义了一个算法的骨架,将某些步骤延迟到子类中实现。这样可以在不改变算法整体结构的情况下,让子类重新定义算法中的某些特定步骤。
打个比方:泡一杯咖啡和泡一杯茶的流程惊人地相似——都需要烧水、冲泡、倒入杯中、添加配料。区别在于“冲泡”的具体内容(咖啡粉 vs 茶叶)和“添加配料”(糖奶 vs 柠檬)。模板方法模式就把这些相似流程抽象成一个骨架,而把具体变化的部分交给子类去完成。
核心思想
- 不变的部分:算法的“骨架”,由父类中的模板方法定义。
- 可变的部分:某些特定步骤的实现,由子类提供。
模式结构
模板方法模式包含以下角色:
- 抽象类(AbstractClass):定义模板方法,并实现算法中不变的公共步骤。同时声明抽象方法,供子类实现差异化逻辑。
- 具体类(ConcreteClass):实现抽象类声明的抽象方法,完善算法的具体行为。
+---------------------+
| AbstractClass |
|---------------------|
| + templateMethod() |<-- 模板方法(final,防止子类修改算法骨架)
| + step1() |
| + step2() |<-- 某些步骤可以是抽象方法
| + hook() |<-- 钩子方法(可选,提供默认行为)
+---------------------+
▲
|
+---------------------+
| ConcreteClass |
|---------------------|
| + step2() |<-- 实现抽象方法
| + hook() |<-- 可选重写钩子
+---------------------+
- 模板方法 通常声明为
final,防止子类修改算法顺序。 - 抽象方法 强制子类提供实现。
- 钩子方法(Hook) 提供默认(通常是空)行为,子类可以选择覆盖以干预算法走向。
动手实现一个示例
我们用 Java 演示一个“数据挖掘”的例子:从不同格式文件(CSV、PDF)中提取数据并生成报告。流程固定为:打开文件 -> 提取数据 -> 分析数据 -> 生成报告 -> 关闭文件。
步骤 1:创建抽象类
public abstract class DataMiner {
// 模板方法,定义算法骨架
public final void mine(String path) {
openFile(path);
String rawData = extractData();
Data analyzed = analyzeData(rawData);
generateReport(analyzed);
closeFile(path);
}
// 默认实现的步骤(不变部分)
private void openFile(String path) {
System.out.println("打开文件: " + path);
}
private void closeFile(String path) {
System.out.println("关闭文件: " + path);
}
// 抽象方法,子类必须提供实现
protected abstract String extractData();
protected abstract Data analyzeData(String rawData);
// 钩子方法,子类可选重写
protected void generateReport(Data data) {
System.out.println("生成默认报告: " + data);
}
}
步骤 2:实现具体类
// CSV 数据挖掘器
public class CsvDataMiner extends DataMiner {
@Override
protected String extractData() {
System.out.println("从CSV中提取数据...");
return "csv,data,content";
}
@Override
protected Data analyzeData(String rawData) {
System.out.println("分析CSV数据...");
return new Data("CSV分析结果");
}
// 使用默认的 generateReport
}
// PDF 数据挖掘器
public class PdfDataMiner extends DataMiner {
@Override
protected String extractData() {
System.out.println("从PDF中提取数据...");
return "pdf text extracted";
}
@Override
protected Data analyzeData(String rawData) {
System.out.println("分析PDF数据...");
return new Data("PDF分析结果");
}
@Override
protected void generateReport(Data data) {
System.out.println("生成PDF专属报告: " + data);
}
}
步骤 3:客户端调用
public class Client {
public static void main(String[] args) {
DataMiner csvMiner = new CsvDataMiner();
csvMiner.mine("data.csv");
System.out.println("---");
DataMiner pdfMiner = new PdfDataMiner();
pdfMiner.mine("report.pdf");
}
}
输出:
打开文件: data.csv
从CSV中提取数据...
分析CSV数据...
生成默认报告: CSV分析结果
关闭文件: data.csv
---
打开文件: report.pdf
从PDF中提取数据...
分析PDF数据...
生成PDF专属报告: PDF分析结果
关闭文件: report.pdf
钩子方法的妙用
钩子方法让模板方法更加灵活。例如,我们可以在抽象类中添加一个 boolean isReportRequired() 钩子,允许子类决定是否生成报告。
public abstract class DataMiner {
// ... 其他方法
// 钩子:是否需要生成报告
protected boolean isReportRequired() {
return true; // 默认需要
}
public final void mine(String path) {
openFile(path);
String rawData = extractData();
Data analyzed = analyzeData(rawData);
if (isReportRequired()) { // 钩子控制
generateReport(analyzed);
}
closeFile(path);
}
}
子类通过覆盖钩子来改变行为:
public class CsvDataMiner extends DataMiner {
// ...
@Override
protected boolean isReportRequired() {
return false; // CSV挖掘不生成报告
}
}
这样,CsvDataMiner 在执行中将跳过报告生成步骤。
模板方法模式的优缺点
优点
- 复用代码:将公共行为提取到父类,避免重复代码。
- 控制扩展:只允许子类改变特定步骤,算法整体结构受保护。
- 符合开闭原则:通过增加子类扩展功能,无需修改现有代码。
- 好莱坞原则:“别调用我们,我们会调用你”。父类控制流程,子类只提供实现。
缺点
- 类数量增加:每新增一种算法变体都要新建子类。
- 灵活性受限:算法骨架固定,如果需要调整步骤顺序就不得不修改抽象类(违反开闭原则)。
- 设计复杂度:父类中的抽象方法和钩子方法需要提前规划。
实际应用场景
模板方法模式随处可见,特别是在框架开发中:
- Java Servlet:
service()方法作为模板,doGet(),doPost()是留给子类的具体步骤。 - Spring 框架:各种
Template类(如JdbcTemplate、RestTemplate)封装了资源获取、异常处理等不便步骤,用户只需提供一小段回调。 - 单元测试框架:JUnit 的
setUp(),tearDown()就是在runBare()模板中的钩子。 - 游戏开发:初始化、游戏循环、渲染、更新状态等固定流水线。
与其他模式的关系
- 工厂方法模式 常被模板方法模式调用,用于创建算法中需要的产品对象。
- 策略模式 是通过组合改变整个算法,而模板方法通过继承改变部分步骤。策略模式更动态,模板方法更静态但结构清晰。
- 建造者模式 也关注一步步构建,但建造者偏向于对象的装配过程,模板方法偏向于算法流程控制。
总结
模板方法模式通过将算法骨架固定在父类中,并让子类填充细节,实现了代码复用和控制反转。它非常适合那些算法结构稳定、个别步骤存在变化的场景。当代框架的“填空式”开发体验,很多都源自这个简单而强大的模式。
下次当你继承一个框架类,并重写其中某个
doXXX()方法时,你就知道正身处模板方法模式的舞台之上。
延伸练习
- 为示例中的数据挖掘器增加一个“数据清洗”步骤,并在 PDF 挖掘器中跳过该步骤。
- 为什么模板方法常标记为
final?如果去掉final会有什么风险? - 在你自己项目中找出一段多个方法流程相似的代码,尝试用模板方法模式重构。