模板方法模式属于行为型设计模式,它在一个抽象类中定义算法的骨架,将某些步骤延迟到子类实现。在Java里,这种控制力主要来自抽象类与final方法的配合:父类把流程顺序锁死,子类只能填充细节,从而既保证一致性又减少重复代码。

一、模板方法模式的基本结构
模板方法模式包含两种角色。一是抽象类,它声明模板方法(通常用final修饰)以及若干基本方法,基本方法可以是抽象方法、具体方法或钩子方法。二是具体子类,它继承抽象类并实现其中的抽象方法,或覆盖钩子方法以影响流程分支。
之所以使用抽象类而不是接口,是因为Java接口在JDK 8之前不能有方法体,难以承载固定的流程代码。抽象类允许我们写出通用的前置处理、后置处理逻辑,只在关键节点调用子类方法。这种“父类调用子类”的反向控制,正是框架常用的好莱坞原则:别调用我们,我们会调用你。
1.1 抽象类中的方法分类
抽象方法:强制子类提供实现,例如do_parse()。具体方法:父类已写好,子类直接复用,如日志记录。钩子方法:父类提供空实现或默认返回true,子类可选择性覆盖,用来干预流程,例如need_validate()。
合理搭配这三种方法,可以让模板既稳定又灵活。如果所有步骤都是抽象方法,子类负担过重;如果全是具体方法,则失去扩展意义。实际项目中多以一个final模板方法串联多个钩子与抽象方法。
二、抽象类在流程控制中的应用示例
下面以支付清算为例。每日批处理需要先加载数据、再做校验、执行清算、最后归档。这个顺序绝不能乱,因此我们把顺序写在抽象类的final方法中,把校验规则和清算方式留给不同渠道子类。
public abstract class AbstractSettlement {
// 模板方法,禁止子类修改顺序
public final void process(String date) {
loadData(date);
if (needValidate()) {
validate();
}
settle();
archive();
}
private void loadData(String date) {
System.out.println("加载" + date + "交易数据");
}
// 钩子方法,默认需要校验
protected boolean needValidate() {
return true;
}
protected abstract void validate();
protected abstract void settle();
private void archive() {
System.out.println("归档并完成记账");
}
}
上述代码中,process是模板方法,用final修饰以防子类重写破坏流程。needValidate是钩子,子类可返回false跳过校验。validate与settle是抽象方法,由子类完成。
接着写两个具体子类,分别处理银行卡与第三方钱包渠道。它们只关心自己特有的校验与清算规则,不必重复写加载和归档。
public class BankCardSettlement extends AbstractSettlement {
@Override
protected void validate() {
System.out.println("校验银行卡交易签名");
}
@Override
protected void settle() {
System.out.println("调用核心系统完成借记");
}
}
public class WalletSettlement extends AbstractSettlement {
@Override
protected boolean needValidate() {
return false; // 钱包内部已验签,跳过
}
@Override
protected void validate() {
// 不会被执行
}
@Override
protected void settle() {
System.out.println("更新钱包余额账务");
}
}
2.1 客户端调用方式
调用方只需面向抽象类编程,根据配置选择子类。这样新增渠道时,只需增加子类,不动原有流程。
public class Client {
public static void main(String[] args) {
AbstractSettlement s1 = new BankCardSettlement();
s1.process("20240101");
AbstractSettlement s2 = new WalletSettlement();
s2.process("20240101");
}
}
从输出可以看到,两个子类的加载与归档逻辑完全一致,差异只在校验与清算。抽象类在此承担了流程守门员的职责。
三、与策略模式的区别及适用场景
初学者常把模板方法模式和策略模式混淆。策略模式是把整个算法替换掉,通过组合不同策略对象来改变行为;模板方法模式是算法骨架不变,仅替换其中部分步骤,且依赖继承而非组合。
当你需要控制多个步骤的执行次序、并复用大量公共前置后置代码时,模板方法更合适。例如各类批处理、流水线作业、框架的初始化生命周期。若只是单一算法可互换,如排序方式、折扣计算,则策略模式更轻量。
3.1 优缺点分析
优点在于代码复用率高、流程统一可控、符合开闭原则。缺点是父类与子类耦合较强,子类受制于父类结构;如果继承层级过深,调试时调用栈不够直观。
在大型系统里,建议把模板方法限制在单一抽象层,不要在子类中再派生孙子类去改流程,否则容易违背单一职责。配合Spring等容器,也可把子类声明为Bean,由工厂注入,缓解硬编码问题。
四、在抽象类中运用钩子增强灵活性
钩子方法常被忽略,但它是模板方法模式避免僵化的关键。通过返回布尔值或空实现,父类可询问子类“要不要做某步”“做完某步后是否追加处理”。
protected void afterSettle() {
// 空钩子,子类可覆盖发通知
}
在模板方法末尾调用afterSettle(),默认什么都不做。若某渠道清算后需推送短信,只需覆盖该方法。这样既不强迫所有子类实现,也保留了扩展点。
总结来说,Java模板方法模式借助抽象类的final方法与继承机制,把流程控制权保留在父类,把可变逻辑下放子类。只要分清抽象方法、具体方法与钩子方法,就能写出既规范又易维护的业务骨架。