跳到主要内容

模板方法模式(Template Method)

模板方法模式是一种行为型设计模式,它在父类中定义算法的骨架,将某些步骤的实现延迟到子类中。模板方法模式让子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。

模式定义

模板方法模式(Template Method Pattern):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

核心要点

  1. 算法骨架:在父类中定义算法的整体结构
  2. 延迟实现:将可变步骤延迟到子类实现
  3. 代码复用:复用不变的部分
  4. 控制反转:父类调用子类的方法

问题场景

假设我们需要实现不同类型的数据处理流程,它们有相似的处理步骤,但具体实现不同:

// 问题代码:重复的处理流程
public class CsvDataProcessor {
public void process(String filename) {
openFile(filename);
readData();
parseData();
validateData();
saveData();
closeFile();
}

private void openFile(String filename) { System.out.println("打开文件: " + filename); }
private void readData() { System.out.println("读取 CSV 数据"); }
private void parseData() { System.out.println("解析 CSV 格式"); }
private void validateData() { System.out.println("验证数据"); }
private void saveData() { System.out.println("保存到数据库"); }
private void closeFile() { System.out.println("关闭文件"); }
}

public class JsonDataProcessor {
public void process(String filename) {
openFile(filename); // 相同
readData(); // 相同
parseData(); // 不同:解析 JSON
validateData(); // 相同
saveData(); // 相同
closeFile(); // 相同
}

// 重复的代码...
}

public class XmlDataProcessor {
// 又是重复的代码...
}

存在的问题

  • 大量重复代码
  • 处理流程变化需要修改多个类
  • 无法统一管理处理流程

解决方案

使用模板方法模式,将公共流程抽象到父类:

// 抽象类定义算法骨架
public abstract class DataProcessor {

// 模板方法:定义算法骨架(final 防止子类覆盖)
public final void process(String filename) {
openFile(filename);
readData();
parseData(); // 抽象方法,子类实现
validateData(); // 可选钩子方法
saveData();
closeFile();
}

// 具体方法:所有子类共用
private void openFile(String filename) {
System.out.println("打开文件: " + filename);
}

private void readData() {
System.out.println("读取数据");
}

// 抽象方法:子类必须实现
protected abstract void parseData();

// 钩子方法:子类可以选择覆盖
protected void validateData() {
System.out.println("默认验证:检查数据不为空");
}

private void saveData() {
System.out.println("保存到数据库");
}

private void closeFile() {
System.out.println("关闭文件");
}
}

// 具体实现:CSV 处理器
public class CsvDataProcessor extends DataProcessor {
@Override
protected void parseData() {
System.out.println("解析 CSV 格式:按逗号分隔");
}

@Override
protected void validateData() {
System.out.println("CSV 验证:检查列数是否正确");
}
}

// 具体实现:JSON 处理器
public class JsonDataProcessor extends DataProcessor {
@Override
protected void parseData() {
System.out.println("解析 JSON 格式:使用 JSON 解析器");
}
}

// 具体实现:XML 处理器
public class XmlDataProcessor extends DataProcessor {
@Override
protected void parseData() {
System.out.println("解析 XML 格式:使用 DOM 解析器");
}

@Override
protected void validateData() {
System.out.println("XML 验证:检查 XML Schema");
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
DataProcessor csvProcessor = new CsvDataProcessor();
DataProcessor jsonProcessor = new JsonDataProcessor();
DataProcessor xmlProcessor = new XmlDataProcessor();

System.out.println("=== 处理 CSV ===");
csvProcessor.process("data.csv");

System.out.println("\n=== 处理 JSON ===");
jsonProcessor.process("data.json");

System.out.println("\n=== 处理 XML ===");
xmlProcessor.process("data.xml");
}
}

输出

=== 处理 CSV ===
打开文件: data.csv
读取数据
解析 CSV 格式:按逗号分隔
CSV 验证:检查列数是否正确
保存到数据库
关闭文件

=== 处理 JSON ===
打开文件: data.json
读取数据
解析 JSON 格式:使用 JSON 解析器
默认验证:检查数据不为空
保存到数据库
关闭文件

=== 处理 XML ===
打开文件: data.xml
读取数据
解析 XML 格式:使用 DOM 解析器
XML 验证:检查 XML Schema
保存到数据库
关闭文件

模式结构

组成部分

  • AbstractClass(抽象类):定义模板方法和抽象方法
  • ConcreteClass(具体类):实现抽象方法
  • Template Method(模板方法):定义算法骨架
  • Primitive Operation(基本方法):子类需要实现的方法
  • Hook Method(钩子方法):子类可选覆盖的方法

方法类型

1. 抽象方法

子类必须实现的方法:

// 抽象方法
protected abstract void parseData();

2. 具体方法

所有子类共用的方法:

// 具体方法
private void openFile(String filename) {
System.out.println("打开文件: " + filename);
}

3. 钩子方法

子类可选覆盖的方法,通常有默认实现:

// 钩子方法(有默认实现)
protected boolean shouldValidate() {
return true; // 默认需要验证
}

protected void validateData() {
if (shouldValidate()) {
System.out.println("验证数据");
}
}

实现方式

1. 基本实现

// 抽象类
public abstract class Game {
// 模板方法
public final void play() {
initialize();
startPlay();
endPlay();
}

// 抽象方法
protected abstract void initialize();
protected abstract void startPlay();
protected abstract void endPlay();
}

// 具体类:足球
public class Football extends Game {
@Override
protected void initialize() {
System.out.println("足球比赛:准备场地");
}

@Override
protected void startPlay() {
System.out.println("足球比赛:开始比赛");
}

@Override
protected void endPlay() {
System.out.println("足球比赛:比赛结束");
}
}

// 具体类:篮球
public class Basketball extends Game {
@Override
protected void initialize() {
System.out.println("篮球比赛:准备篮球场");
}

@Override
protected void startPlay() {
System.out.println("篮球比赛:开始比赛");
}

@Override
protected void endPlay() {
System.out.println("篮球比赛:比赛结束");
}
}

2. 带钩子方法

public abstract class Beverage {
// 模板方法
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; // 默认添加调料
}
}

// 咖啡
public class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("冲泡咖啡");
}

@Override
protected void addCondiments() {
System.out.println("添加糖和牛奶");
}
}

// 茶
public class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}

@Override
protected void addCondiments() {
System.out.println("添加柠檬");
}

// 覆盖钩子方法
@Override
protected boolean customerWantsCondiments() {
return false; // 茶不加调料
}
}

3. 带默认值的模板方法

public abstract class ReportGenerator {
// 模板方法
public final void generateReport() {
String header = createHeader();
String body = createBody();
String footer = createFooter();

System.out.println(header);
System.out.println(body);
System.out.println(footer);
}

// 抽象方法:必须实现
protected abstract String createBody();

// 带默认实现的方法
protected String createHeader() {
return "=== 报告 ===";
}

protected String createFooter() {
return "=== 报告结束 ===";
}
}

// 销售报告
public class SalesReport extends ReportGenerator {
@Override
protected String createBody() {
return "销售数据:100万";
}

@Override
protected String createHeader() {
return "=== 销售报告 ===";
}
}

// 财务报告
public class FinanceReport extends ReportGenerator {
@Override
protected String createBody() {
return "财务数据:盈利50万";
}
}

实际应用案例

1. Java Servlet

Servlet 的生命周期使用模板方法模式:

public abstract class HttpServlet extends GenericServlet {

// 模板方法:service 方法
protected void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();

if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
} else if (method.equals("PUT")) {
doPut(req, resp);
} else if (method.equals("DELETE")) {
doDelete(req, resp);
}
// ...
}

// 子类覆盖的方法
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 默认实现返回 405
}

protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
// 默认实现返回 405
}

protected void doPut(HttpServletRequest req, HttpServletResponse resp) { }

protected void doDelete(HttpServletRequest req, HttpServletResponse resp) { }
}

// 使用
@WebServlet("/user")
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 处理 GET 请求
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
// 处理 POST 请求
}
}

2. Spring JdbcTemplate

Spring 的 JdbcTemplate 使用模板方法模式:

public abstract class JdbcTemplate {

// 模板方法
public <T> T query(String sql, RowMapper<T> rowMapper) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;

try {
conn = getConnection(); // 获取连接
stmt = conn.prepareStatement(sql);
rs = stmt.executeQuery();

List<T> results = new ArrayList<>();
while (rs.next()) {
results.add(rowMapper.mapRow(rs)); // 回调方法
}
return results.get(0);

} catch (SQLException e) {
throw new DataAccessException(e);
} finally {
closeResultSet(rs); // 关闭资源
closeStatement(stmt);
closeConnection(conn);
}
}

// 抽象方法
protected abstract Connection getConnection();
}

// 使用
jdbcTemplate.query("SELECT * FROM users", (rs) -> {
return new User(rs.getLong("id"), rs.getString("name"));
});

3. Arrays.sort()

Java 的排序使用模板方法模式(策略模式变体):

public class Arrays {
public static <T> void sort(T[] a, Comparator<? super T> c) {
// 排序算法的骨架
// 通过 Comparator 回调比较逻辑
}
}

// 使用
Arrays.sort(users, (a, b) -> a.getName().compareTo(b.getName()));

4. Android Activity 生命周期

Android 的 Activity 生命周期是模板方法模式:

public class Activity {
// 模板方法
protected void onCreate(Bundle savedInstanceState) { }
protected void onStart() { }
protected void onResume() { }
protected void onPause() { }
protected void onStop() { }
protected void onDestroy() { }
}

// 使用
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
super.onResume();
// 恢复操作
}
}

模板方法模式 vs 策略模式

特性模板方法模式策略模式
实现方式继承组合
算法变化部分步骤变化整体算法变化
控制权父类控制流程客户端选择策略
扩展性新增子类新增策略类

优缺点分析

优点

  1. 代码复用:复用公共代码
  2. 统一流程:统一管理算法流程
  3. 扩展性好:新增子类不影响其他类
  4. 控制反转:父类调用子类方法

缺点

  1. 继承限制:每个子类只能有一个父类
  2. 类数量增加:每个变体需要一个子类
  3. 调试困难:流程分散在父类和子类

使用建议

何时使用模板方法模式

  • 多个类有相似的算法流程
  • 需要控制算法的扩展点
  • 需要复用公共代码

何时避免使用模板方法模式

  • 算法流程差异很大
  • 不需要复用代码
  • 继承层次已经很深

最佳实践

  1. 模板方法用 final:防止子类覆盖
  2. 合理使用钩子:提供扩展点
  3. 命名清晰:方法名应体现其作用
  4. 控制抽象方法数量:避免子类负担过重

小结

模板方法模式通过继承实现代码复用:

方法类型特点用途
抽象方法子类必须实现定义可变步骤
具体方法所有子类共用定义不变步骤
钩子方法子类可选覆盖提供扩展点

练习

  1. 实现一个文件解析器,支持不同格式的文件解析
  2. 实现一个排序算法模板,支持不同的比较策略
  3. 实现一个数据库访问模板,统一管理连接和资源
  4. 分析 Spring JdbcTemplate 的设计

参考资源