1. 模板方法模式的核心概念
模板方法模式是行为型设计模式中最基础也最常用的一种。我第一次接触这个模式是在重构一个电商订单处理系统时,当时系统中存在大量重复的订单处理流程代码,每个订单类型都有细微差异但整体流程高度相似。这正是模板方法模式大显身手的场景。
模板方法模式通过定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现。它使得子类可以不改变算法结构的情况下重新定义某些特定步骤。这种模式在Java集合框架、Servlet生命周期等场景中都有广泛应用。
关键点:模板方法模式的核心在于"不变部分封装,可变部分扩展"。父类定义骨架流程,子类实现具体细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模式结构与实现原理
2.1 UML类图解析
标准的模板方法模式包含以下核心角色:
- AbstractClass(抽象类):定义算法骨架和基本操作
- ConcreteClass(具体子类):实现父类定义的抽象操作
java复制// 抽象类定义模板方法
public abstract class AbstractClass {
// 模板方法,定义算法骨架
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}
// 基本方法,由子类实现
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
// 具体方法,已实现
protected final void concreteOperation() {
// 实现代码
}
// 钩子方法,可选实现
protected void hook() {}
}
2.2 方法类型详解
在模板方法模式中,方法可以分为三类:
- 抽象方法:必须由子类实现
- 具体方法:父类已实现,子类可直接使用
- 钩子方法:父类提供默认空实现,子类可选择覆盖
设计技巧:将不希望子类修改的方法声明为final,如模板方法和具体方法。这是保证算法骨架不被破坏的关键。
3. 实战应用案例
3.1 电商订单处理系统
假设我们需要处理多种类型的订单(普通订单、团购订单、秒杀订单),它们的处理流程相似但某些步骤不同:
java复制public abstract class OrderProcessor {
// 模板方法
public final void processOrder() {
validate();
deductInventory();
calculatePrice();
if(needLog()) {
writeLog();
}
notifyUser();
}
protected abstract void validate();
protected abstract void calculatePrice();
protected void deductInventory() {
// 默认库存扣减逻辑
}
// 钩子方法
protected boolean needLog() {
return true;
}
protected void writeLog() {
System.out.println("记录操作日志");
}
protected void notifyUser() {
System.out.println("发送短信通知");
}
}
// 具体实现
public class GroupOrderProcessor extends OrderProcessor {
@Override
protected void validate() {
System.out.println("验证团购资格");
}
@Override
protected void calculatePrice() {
System.out.println("计算团购价格");
}
@Override
protected boolean needLog() {
return false; // 团购订单不需要记录日志
}
}
3.2 JDBC模板应用
Spring框架中的JdbcTemplate是模板方法模式的经典实现。它定义了执行SQL的流程(获取连接、创建语句、执行、处理结果、释放资源),而将结果集处理等可变部分交给回调接口:
java复制public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
return action.doInConnection(con);
} catch (SQLException ex) {
throw translateException("ConnectionCallback", ex);
} finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
4. 模式优势与适用场景
4.1 主要优势
- 代码复用:将公共行为提取到父类,避免代码重复
- 扩展可控:通过钩子方法提供灵活扩展点
- 反向控制:符合好莱坞原则("不要调用我们,我们会调用你")
- 便于维护:算法结构集中管理,修改只需调整父类
4.2 典型应用场景
- 多个类有相似算法,但某些步骤实现不同
- 需要控制子类扩展的粒度时
- 框架设计中的流程标准化
- 重构时消除重复代码
5. 实现细节与注意事项
5.1 方法可见性设计
正确设置方法的访问修饰符至关重要:
- 模板方法:public final
- 具体方法:protected final
- 抽象方法:protected abstract
- 钩子方法:protected
5.2 常见误区与解决方案
问题1:子类意外覆盖模板方法
java复制// 错误示例
public class SubClass extends ParentClass {
public void templateMethod() { // 意外覆盖
// 自定义实现
}
}
解决方案:将父类的模板方法声明为final
问题2:过度使用钩子方法导致流程混乱
解决方案:限制钩子方法数量,每个钩子应有明确单一职责
问题3:模板方法过于庞大
解决方案:将大模板方法拆分为多个小模板方法
6. 模式变体与相关模式
6.1 带回调的模板方法
结合策略模式,将可变部分提取为接口:
java复制public interface StepCallback {
void execute();
}
public class Template {
public void process(StepCallback callback) {
step1();
callback.execute();
step2();
}
}
6.2 相关模式对比
- 策略模式:完整替换算法
- 模板方法:部分步骤可变
- 工厂方法:可视为模板方法的特例
7. 性能考量与最佳实践
7.1 性能影响
模板方法模式通过继承实现,方法调用是直接的,没有额外的性能开销。但需要注意:
- 避免在模板方法中包含重量级操作
- 合理设计抽象方法的粒度
- 考虑使用final方法提升JVM优化空间
7.2 最佳实践
- 文档化模板:清晰说明哪些方法必须/可以/不能覆盖
- 命名约定:使用前缀如"do"或"handle"标识可覆盖方法
- 单元测试:为模板方法和各种实现组合编写测试用例
- 组合优于继承:复杂场景考虑使用组合+策略模式替代
8. 真实项目中的经验分享
在金融交易系统中,我们使用模板方法模式处理不同类型的交易指令。以下是几个关键经验:
- 版本兼容:在父类中添加新方法时,提供默认实现保持向后兼容
- 日志追踪:在模板方法中加入调试日志,方便排查问题
- 异常处理:统一在模板方法中处理异常,避免重复代码
- 性能监控:在模板方法中埋点监控各步骤耗时
java复制public abstract class TransactionTemplate {
public final Result execute() {
long start = System.currentTimeMillis();
try {
validate();
process();
audit();
return buildResult();
} catch (Exception e) {
handleException(e);
throw e;
} finally {
logDuration(System.currentTimeMillis() - start);
}
}
// 其他抽象方法...
}
模板方法模式看似简单,但要设计出既灵活又稳定的模板需要实践经验。我建议从小的功能点开始尝试,逐步掌握如何平衡结构的刚性和扩展的灵活性。
