设计模式 - 模板模式
引言
在软件开发中,我们经常会遇到这样的场景:多个类有相似的业务流程,但在某些具体步骤上实现不同。如果每个类都重复编写相同的流程代码,不仅冗余,而且难以维护。这时,**模板模式(Template Pattern)**就派上用场了。
模板模式是行为型设计模式之一,它通过定义算法的骨架,将某些步骤的实现延迟到子类中,从而在不改变算法结构的前提下,让子类重新定义算法的某些特定步骤。
什么是模板模式
模板模式的核心思想非常简单:在一个方法中定义算法的骨架,将某些步骤推迟到子类中实现。
更通俗地说,模板模式就像是一份”填空题”:父类已经把题目的框架和大部分内容都写好了,只留下几个空让子类来填。这样既保证了整体结构的一致性,又提供了足够的灵活性。
模式结构
模板模式主要包含两种角色:
抽象类(Abstract Class):定义算法的骨架,包含模板方法和基本方法
- 模板方法:定义算法骨架,调用基本方法
- 基本方法:抽象方法(由子类实现)或具体方法(子类可重写)
具体类(Concrete Class):实现抽象类中的抽象方法,完成特定步骤的具体实现
实战示例:游戏流程模板
让我们通过一个游戏流程的例子来深入理解模板模式。
场景描述
假设我们需要开发多种游戏,每种游戏的流程都包含三个步骤:初始化、开始游戏、结束游戏。虽然不同游戏的具体实现不同,但整体流程是一样的。
代码实现
1. 定义抽象模板类
public abstract class GameTemplate {
/**
* 模板方法:定义游戏流程的骨架
* 使用final修饰,防止子类重写算法结构
*/
public final void play() {
initialize();
startPlay();
endPlay();
}
/**
* 抽象方法:初始化游戏
* 由子类具体实现
*/
protected abstract void initialize();
/**
* 抽象方法:开始游戏
* 由子类具体实现
*/
protected abstract void startPlay();
/**
* 具体方法:结束游戏
* 子类可以直接使用,也可以重写
*/
protected void endPlay() {
System.out.println("游戏结束,感谢游玩!");
}
}
2. 实现具体游戏类
足球游戏:
public class Football extends GameTemplate {
@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 GameTemplate {
@Override
protected void initialize() {
System.out.println("篮球游戏初始化:准备篮球场地、篮筐、球员");
}
@Override
protected void startPlay() {
System.out.println("篮球游戏开始:比赛进行中...");
}
// 使用父类的默认endPlay实现
}
3. 客户端调用
public class GameDemo {
public static void main(String[] args) {
System.out.println("=== 足球游戏 ===");
GameTemplate football = new Football();
football.play();
System.out.println("\n=== 篮球游戏 ===");
GameTemplate basketball = new Basketball();
basketball.play();
}
}
输出结果:
=== 足球游戏 ===
足球游戏初始化:准备足球场地、球门、球员
足球游戏开始:比赛进行中...
足球比赛结束:全场比赛结束!
=== 篮球游戏 ===
篮球游戏初始化:准备篮球场地、篮筐、球员
篮球游戏开始:比赛进行中...
游戏结束,感谢游玩!
代码解析
- 模板方法
play():使用final修饰,确保算法骨架不被修改 - 抽象方法:
initialize()和startPlay()强制子类实现,保证灵活性 - 具体方法:
endPlay()提供默认实现,子类可选择重写或直接使用 - 访问控制:使用
protected修饰,子类可以访问,外部不可直接调用
进阶示例:数据库操作模板
让我们看一个更实用的例子:数据库操作的模板模式实现。
public abstract class DatabaseTemplate {
/**
* 模板方法:执行数据库操作的完整流程
*/
public final void execute() {
Connection connection = null;
try {
// 1. 获取连接
connection = getConnection();
// 2. 执行具体操作
doExecute(connection);
// 3. 提交事务
commit(connection);
} catch (Exception e) {
// 4. 异常处理
rollback(connection);
handleError(e);
} finally {
// 5. 关闭连接
closeConnection(connection);
}
}
// 基本方法
protected abstract Connection getConnection() throws SQLException;
protected abstract void doExecute(Connection connection) throws SQLException;
// 具体方法(提供默认实现)
protected void commit(Connection connection) throws SQLException {
if (connection != null) {
connection.commit();
}
}
protected void rollback(Connection connection) {
try {
if (connection != null) {
connection.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
protected void closeConnection(Connection connection) {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
protected void handleError(Exception e) {
System.err.println("数据库操作失败:" + e.getMessage());
e.printStackTrace();
}
}
使用示例:
public class UserDao extends DatabaseTemplate {
@Override
protected Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
}
@Override
protected void doExecute(Connection connection) throws SQLException {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, "张三");
stmt.setString(2, "zhangsan@example.com");
stmt.executeUpdate();
System.out.println("用户数据插入成功");
}
}
钩子方法
模板模式中还有一个重要概念:钩子方法(Hook Method)。钩子方法是抽象类中定义的具体方法,通常返回布尔值或空实现,子类可以重写这些方法来影响模板方法的执行流程。
示例:带钩子的游戏模板
public abstract class GameWithHook {
public final void play() {
initialize();
// 钩子方法控制是否显示规则
if (shouldShowRules()) {
showRules();
}
startPlay();
endPlay();
}
protected abstract void initialize();
protected abstract void startPlay();
protected void endPlay() {
System.out.println("游戏结束!");
}
// 钩子方法:子类可重写
protected boolean shouldShowRules() {
return true;
}
// 具体方法
private void showRules() {
System.out.println("显示游戏规则...");
}
}
// 使用钩子
public class ChessGame extends GameWithHook {
@Override
protected boolean shouldShowRules() {
return false; // 象棋不显示规则
}
@Override
protected void initialize() {
System.out.println("初始化象棋游戏");
}
@Override
protected void startPlay() {
System.out.println("开始下棋");
}
}
应用场景
模板模式在实际开发中应用非常广泛,以下是一些典型场景:
1. 框架和库的设计
- Spring JdbcTemplate:封装了数据库操作的通用流程(获取连接、执行SQL、处理结果集、关闭连接)
- HttpServlet:
service()方法根据请求类型调用doGet()、doPost()等方法
2. 业务流程处理
- 订单处理流程:验证订单 → 扣减库存 → 计算价格 → 支付 → 发货
- 审批流程:提交申请 → 各级审批 → 通知结果
- 报表生成:查询数据 → 处理数据 → 生成报表 → 导出文件
3. 算法实现
- 排序算法:不同排序算法有相同的骨架,但比较和交换的具体实现不同
- 数据处理管道:读取 → 转换 → 过滤 → 输出
4. 测试框架
- JUnit:测试的生命周期(setUp → test → tearDown)
- 测试报告生成:收集结果 → 生成统计 → 输出报告
优点与缺点
优点
- 代码复用:将公共代码提取到父类,避免重复
- 反向控制:父类调用子类的方法,符合”好莱坞原则”(Don’t call us, we’ll call you)
- 扩展性好:新增功能只需新增子类,符合开闭原则
- 维护性强:修改算法骨架只需修改父类,不影响子类
缺点
- 类数量增加:每个实现都需要一个子类,可能导致类数量膨胀
- 继承限制:Java 单继承限制,子类无法继承其他类
- 理解难度:如果模板方法逻辑复杂,可能增加理解成本
- 调试困难:调用栈跨越多个类,调试时需要跳转
最佳实践
1. 模板方法使用 final
// 好的做法
public final void templateMethod() {
step1();
step2();
step3();
}
// 不好的做法:允许子类修改算法结构
public void templateMethod() {
step1();
step2();
step3();
}
2. 合理使用访问修饰符
public abstract class Template {
// 模板方法:公开
public final void templateMethod() { ... }
// 基本方法:protected,子类可访问,外部不可调用
protected abstract void step1();
// 钩子方法:protected,子类可重写
protected boolean shouldExecuteStep2() { return true; }
// 辅助方法:private,仅本类使用
private void helperMethod() { ... }
}
3. 合理设计抽象方法和具体方法
- 抽象方法:必须由子类实现的核心逻辑
- 具体方法:通用的、不易变化的逻辑
- 钩子方法:提供扩展点,但不强制重写
4. 避免过度使用
不是所有相似逻辑都需要模板模式,如果:
- 代码复用不多
- 流程简单
- 未来变化不大
那么直接继承或组合可能更简单。
模板模式 vs 其他模式
vs 策略模式
- 模板模式:通过继承实现,算法骨架固定,部分步骤可变
- 策略模式:通过组合实现,整个算法可替换
vs 建造者模式
- 模板模式:关注算法流程控制
- 建造者模式:关注对象的构建过程
vs 工厂方法模式
- 模板模式:行为型模式,定义算法骨架
- 工厂方法模式:创建型模式,定义对象创建接口
总结
模板模式是一种简单而强大的设计模式,它通过定义算法骨架并将具体实现延迟到子类,实现了代码复用和扩展性的平衡。在实际开发中,当我们发现多个类有相似的流程但某些步骤不同时,模板模式是一个很好的选择。
核心要点:
- 模板方法定义算法骨架,使用
final修饰防止修改 - 抽象方法强制子类实现,保证灵活性
- 具体方法提供默认实现,子类可选择性重写
- 钩子方法提供扩展点,控制流程执行
记住: 设计模式不是银弹,要根据实际场景选择是否使用。模板模式适用于流程固定、部分步骤变化的场景,如果逻辑过于简单或变化太大,可能不需要使用模板模式。
参考资料
- 《设计模式:可复用面向对象软件的基础》- GoF
- 《Head First 设计模式》
- Spring Framework Documentation
- Effective Java (Third Edition)
作者:leejie
日期:2026年
标签:Java、设计模式、模板模式、架构设计