1. 模板方法模式的核心思想
在软件开发中,我们经常会遇到这样的情况:多个类执行的操作步骤基本相同,但每个步骤的具体实现又有所不同。这种场景下,模板方法模式就能大显身手了。
模板方法模式是一种行为设计模式,它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样可以在不改变算法结构的情况下,让子类重新定义算法的某些特定步骤。
关键提示:模板方法模式的核心在于"不变"与"可变"的分离。不变的算法结构放在父类,可变的具体实现交给子类。
1.1 模式结构解析
一个典型的模板方法模式包含以下关键组件:
-
抽象父类(Abstract Class):
- 定义算法骨架(模板方法)
- 声明抽象方法(由子类实现)
- 提供钩子方法(可选步骤)
-
具体子类(Concrete Class):
- 实现父类定义的抽象方法
- 可以覆盖钩子方法
- 不能改变模板方法的执行流程
java复制// 抽象父类示例
public abstract class AbstractClass {
// 模板方法,定义算法骨架
public final void templateMethod() {
step1();
step2();
step3();
}
// 基本方法,子类必须实现
protected abstract void step1();
// 基本方法,子类必须实现
protected abstract void step2();
// 默认实现,子类可选择覆盖
protected void step3() {
// 默认实现
}
}
1.2 模式的优势与适用场景
主要优势:
- 代码复用:将公共代码抽取到父类,避免重复
- 扩展性好:新增子类只需实现差异部分
- 控制反转:父类控制流程,子类提供实现
典型应用场景:
- 多个类有相似流程但实现不同
- 需要固定算法执行顺序
- 需要扩展算法中的特定步骤
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 抽取通用代码到父类的实践
2.1 识别可抽取的公共逻辑
在实际项目中应用模板方法模式,首先要识别哪些代码是可以抽取的公共逻辑。以下是一些常见的可抽取点:
- 算法流程:多个类中执行步骤相同的操作流程
- 前置/后置处理:如资源初始化、清理工作
- 异常处理:统一的异常捕获和处理机制
- 日志记录:操作前后的日志记录
- 性能监控:方法执行时间的统计
2.2 重构步骤详解
让我们通过一个实际案例来演示如何将通用代码抽取到父类:
重构前:
java复制// 订单处理类
public class OrderProcessor {
public void processOrder(Order order) {
// 1. 验证订单
validateOrder(order);
// 2. 计算价格
calculatePrice(order);
// 3. 生成发票
generateInvoice(order);
// 4. 通知客户
notifyCustomer(order);
}
private void validateOrder(Order order) { /*...*/ }
private void calculatePrice(Order order) { /*...*/ }
private void generateInvoice(Order order) { /*...*/ }
private void notifyCustomer(Order order) { /*...*/ }
}
// 退货处理类
public class ReturnProcessor {
public void processReturn(Return return) {
// 1. 验证退货
validateReturn(return);
// 2. 计算退款
calculateRefund(return);
// 3. 生成退款单
generateRefund(return);
// 4. 通知客户
notifyCustomer(return);
}
private void validateReturn(Return return) { /*...*/ }
private void calculateRefund(Return return) { /*...*/ }
private void generateRefund(Return return) { /*...*/ }
private void notifyCustomer(Return return) { /*...*/ }
}
重构后:
java复制// 抽象父类
public abstract class TransactionProcessor {
// 模板方法
public final void processTransaction(Transaction transaction) {
validate(transaction);
calculate(transaction);
generateDocument(transaction);
notifyCustomer(transaction);
}
protected abstract void validate(Transaction transaction);
protected abstract void calculate(Transaction transaction);
protected abstract void generateDocument(Transaction transaction);
// 钩子方法
protected void notifyCustomer(Transaction transaction) {
// 默认实现
}
}
// 具体子类:订单处理
public class OrderProcessor extends TransactionProcessor {
@Override
protected void validate(Transaction transaction) { /*...*/ }
@Override
protected void calculate(Transaction transaction) { /*...*/ }
@Override
protected void generateDocument(Transaction transaction) { /*...*/ }
@Override
protected void notifyCustomer(Transaction transaction) {
// 定制化实现
}
}
// 具体子类:退货处理
public class ReturnProcessor extends TransactionProcessor {
@Override
protected void validate(Transaction transaction) { /*...*/ }
@Override
protected void calculate(Transaction transaction) { /*...*/ }
@Override
protected void generateDocument(Transaction transaction) { /*...*/ }
}
2.3 重构后的优势分析
- 代码量减少:公共流程只定义一次,子类只需关注差异部分
- 维护性提升:修改流程只需调整父类,不影响子类
- 一致性保证:所有子类都遵循相同的处理流程
- 灵活性保留:子类可以覆盖特定步骤或使用钩子方法
3. 模板方法模式的进阶技巧
3.1 钩子方法的巧妙运用
钩子方法(Hook Method)是模板方法模式中的一个重要概念,它提供了额外的扩展点:
java复制public abstract class ReportGenerator {
// 模板方法
public final void generateReport() {
initialize();
generateHeader();
generateBody();
if (needFooter()) { // 钩子方法
generateFooter();
}
finalizeReport();
}
protected abstract void generateHeader();
protected abstract void generateBody();
// 钩子方法:默认返回true
protected boolean needFooter() {
return true;
}
protected void generateFooter() { /* 默认实现 */ }
}
钩子方法的典型用途:
- 控制可选步骤的执行
- 提供子类影响父类行为的途径
- 实现"好莱坞原则"(不要调用我们,我们会调用你)
3.2 模板方法的访问控制
合理设置方法的访问修饰符对模板方法模式至关重要:
- 模板方法:通常声明为
final,防止子类修改算法结构 - 基本方法:声明为
abstract,强制子类实现 - 钩子方法:提供默认实现,声明为
protected - 具体方法:完成固定逻辑,声明为
private或final protected
3.3 与策略模式的对比
模板方法模式和策略模式都用于封装算法,但有以下区别:
| 特性 | 模板方法模式 | 策略模式 |
|---|---|---|
| 实现方式 | 继承 | 组合 |
| 运行时灵活性 | 编译时确定 | 运行时可切换 |
| 代码复用 | 通过继承复用父类代码 | 策略类之间无复用 |
| 适用场景 | 算法步骤固定,部分实现不同 | 需要动态切换完整算法 |
4. 实际项目中的经验分享
4.1 常见问题与解决方案
问题1:子类过多导致类爆炸
- 解决方案:合理设计抽象层级,考虑使用组合+模板方法的混合模式
问题2:模板方法过于复杂
- 解决方案:拆分大模板方法为多个小模板方法
问题3:需要改变算法流程
- 解决方案:考虑使用策略模式替代,或在父类中提供多个模板方法
4.2 性能优化技巧
- 缓存计算结果:在父类中实现缓存逻辑,子类无需关心
- 延迟加载:将资源加载放到模板方法中统一管理
- 批量处理:在父类中实现批量处理逻辑,提升性能
java复制public abstract class BatchProcessor {
private final int BATCH_SIZE = 100;
public final void processAll(List<Item> items) {
for (int i = 0; i < items.size(); i += BATCH_SIZE) {
List<Item> batch = items.subList(i, Math.min(i + BATCH_SIZE, items.size()));
processBatch(batch);
}
}
protected abstract void processBatch(List<Item> batch);
}
4.3 测试注意事项
- 测试父类:确保模板方法的流程正确
- 测试子类:验证具体实现是否符合预期
- 测试钩子方法:覆盖所有可能的执行路径
- 使用Mock对象:测试子类时隔离父类依赖
java复制// 测试示例
public class TemplateMethodTest {
@Test
public void testTemplateMethodFlow() {
AbstractClass instance = new ConcreteClass();
instance.templateMethod();
// 验证执行流程
}
@Test
public void testHookMethodOverride() {
AbstractClass instance = new ConcreteClassWithHook();
instance.templateMethod();
// 验证钩子方法效果
}
}
5. 现代Java中的模板方法模式
5.1 结合函数式编程
Java 8引入的lambda表达式和函数式接口可以与模板方法模式结合使用:
java复制public abstract class FunctionalTemplate {
public final void execute(Consumer<String> logger) {
preProcess();
logger.accept("Processing started");
doProcess();
logger.accept("Processing completed");
postProcess();
}
protected abstract void preProcess();
protected abstract void doProcess();
protected abstract void postProcess();
}
5.2 与Stream API的结合
模板方法模式可以很好地处理Stream操作中的通用流程:
java复制public abstract class StreamProcessor<T> {
public final void processStream(Stream<T> stream) {
stream.filter(this::filterCondition)
.map(this::transform)
.forEach(this::handle);
}
protected abstract boolean filterCondition(T item);
protected abstract T transform(T item);
protected abstract void handle(T item);
}
5.3 在Spring框架中的应用
Spring框架中大量使用了模板方法模式:
- JdbcTemplate:处理JDBC操作的通用流程
- RestTemplate:处理REST调用的通用流程
- TransactionTemplate:管理事务的通用流程
java复制// Spring JdbcTemplate示例
public class UserDao {
private final JdbcTemplate jdbcTemplate;
public List<User> findAllUsers() {
return jdbcTemplate.query(
"SELECT * FROM users",
(rs, rowNum) -> new User(
rs.getLong("id"),
rs.getString("name"),
rs.getString("email")
)
);
}
}
在实际项目中,我发现合理使用模板方法模式可以显著提升代码的可维护性和可扩展性。特别是在处理具有固定流程但实现细节不同的业务场景时,这种模式能够很好地平衡代码复用和灵活性。
