前言
你有没有遇到过这种情况------
团队里有几个功能,流程几乎一模一样:校验参数 → 查询数据 → 处理逻辑 → 返回结果。 但每个人各写各的,代码散落在四处,改一个公共逻辑要翻好几个文件,改完还容易漏。
这就是没有统一骨架的代价。
而今天要聊的模版方法模式(Template Method Pattern) ,正是专门解决这类问题的------ 把流程骨架锁在父类,把差异化步骤交给子类,既统一规范,又灵活扩展。
一、一句话定义
上层抽象类定义好操作的基本框架(骨架),某些特殊步骤交给子类实现。子类可以在不改变整体流程的前提下,定制其中某些步骤。
说白了就是:父类定规矩,子类填空格。
二、核心结构
模版方法模式只有两个角色,非常简洁:
scss
AbstractClass(抽象类)
├── templateMethod() ← 模版方法:定义流程骨架(final,不可覆盖)
├── method1() ← 抽象方法:留给子类实现
├── method2() ← 具体方法:基类默认实现,子类可覆盖
└── method3() ← 具体方法:基类默认实现
ConcreteClass(具体实现类)
└── method1() ← 实现父类定义的抽象步骤
关键点:
| 方法类型 | 定义位置 | 实现位置 | 说明 |
|---|---|---|---|
| 模版方法 | 抽象类 | 抽象类 | 流程骨架,建议加 final 防止被覆盖 |
| 抽象方法 | 抽象类 | 子类(必须) | 差异化步骤,强制子类实现 |
| 具体方法 | 抽象类 | 抽象类(可被覆盖) | 通用步骤,子类按需重写 |
三、代码实战------从简单到真实场景
3.1 基础版本
先看原始结构,理解骨架:
csharp
/**
* 抽象模版类:定义操作骨架
*/
public abstract class AbstractTemplate {
// 模版方法:锁定流程,不允许子类覆盖
public final void templateMethod() {
method1(); // 抽象步骤,子类实现
method2(); // 通用步骤,基类实现
method3(); // 通用步骤,基类实现
}
// 抽象方法:差异化步骤,强制子类实现
abstract void method1();
// 具体方法:通用逻辑,子类可按需覆盖
public void method2() {
System.out.println("执行 method2... 基类实现");
}
public void method3() {
System.out.println("执行 method3... 基类实现");
}
}
scala
/**
* 具体实现类:只需实现差异化步骤
*/
public class ConcreteClass extends AbstractTemplate {
@Override
void method1() {
System.out.println("子类实现特定步骤...");
}
}
运行:
arduino
public class Main {
public static void main(String[] args) {
AbstractTemplate template = new ConcreteClass();
template.templateMethod();
}
}
输出:
erlang
子类实现特定步骤...
执行 method2... 基类实现
执行 method3... 基类实现
流程由父类控制,子类只管填自己的"空格"。
3.2 真实场景------数据导出功能
假设系统需要支持导出 Excel 和 CSV 两种格式,流程完全一样:
查询数据 → 构建表头 → 填充数据行 → 生成文件
只有"构建表头"和"填充数据行"这两步格式不同,用模版方法处理再合适不过:
typescript
/**
* 数据导出模版
*/
public abstract class DataExportTemplate {
// 模版方法:锁定导出流程
public final void export(String fileName) {
System.out.println("===== 开始导出:" + fileName + " =====");
List<Object> data = queryData(); // 第一步:查询数据(通用)
String header = buildHeader(); // 第二步:构建表头(子类定制)
String content = buildContent(data); // 第三步:填充内容(子类定制)
writeFile(fileName, header, content); // 第四步:写文件(通用)
System.out.println("===== 导出完成 =====");
}
// 通用步骤:查询数据,所有格式一样
private List<Object> queryData() {
System.out.println("从数据库查询数据...");
return List.of("row1", "row2", "row3"); // 模拟数据
}
// 抽象步骤:表头格式由子类决定
protected abstract String buildHeader();
// 抽象步骤:内容格式由子类决定
protected abstract String buildContent(List<Object> data);
// 通用步骤:写文件
private void writeFile(String fileName, String header, String content) {
System.out.println("写入文件:" + fileName);
System.out.println("内容预览:\n" + header + "\n" + content);
}
}
scala
/**
* Excel 导出实现
*/
public class ExcelExport extends DataExportTemplate {
@Override
protected String buildHeader() {
return "ID\t姓名\t部门"; // Excel 用 Tab 分隔
}
@Override
protected String buildContent(List<Object> data) {
StringBuilder sb = new StringBuilder();
data.forEach(row -> sb.append(row).append("\n"));
return sb.toString();
}
}
scala
/**
* CSV 导出实现
*/
public class CsvExport extends DataExportTemplate {
@Override
protected String buildHeader() {
return "ID,姓名,部门"; // CSV 用逗号分隔
}
@Override
protected String buildContent(List<Object> data) {
StringBuilder sb = new StringBuilder();
data.forEach(row -> sb.append(row).append("\r\n"));
return sb.toString();
}
}
调用:
ini
DataExportTemplate excel = new ExcelExport();
excel.export("员工表.xlsx");
DataExportTemplate csv = new CsvExport();
csv.export("员工表.csv");
新增一种导出格式?只需继承 DataExportTemplate,实现两个方法,查询逻辑、写文件逻辑完全复用,一行公共代码都不用改。
四、你早就用过它------框架里的模版方法
模版方法模式在主流框架中随处可见,你可能已经用过很多次,只是没意识到:
Spring 中的 JdbcTemplate 查询 SQL 的流程(获取连接 → 执行 → 映射结果 → 关闭连接)是固定的,你只需实现 RowMapper 接口填充"映射结果"这一步。
Servlet 中的 HttpServlet service() 方法是模版方法,它根据请求类型分发到 doGet()、doPost() 等------你只需覆盖你关心的方法。
MyBatis 中的 BaseExecutor SQL 执行流程由基类控制,缓存查询、结果处理等细节由子类实现。
Spring 生命周期回调 InitializingBean.afterPropertiesSet()、@PostConstruct 本质上也是模版方法的变体------框架控制 Bean 初始化流程,你只填充自己的初始化逻辑。
五、优缺点 & 注意事项
优点
- 消除重复代码:公共流程收归父类,一处修改处处生效
- 强制规范:新增实现类必须遵循统一流程,不会乱来
- 开闭原则:扩展新类型无需修改已有代码
缺点
- 继承的局限:Java 是单继承,子类一旦继承了模版类,就无法再继承其他类
- 流程不灵活:骨架固定,步骤顺序无法由子类调整(这正是它的特性,但有时也是约束)
- 类数量膨胀:每种变体都需要一个子类,场景多了类会很多
注意事项
建议将模版方法声明为
final,明确告诉子类"流程骨架不可修改",防止被意外覆盖破坏整体逻辑。
六、与策略模式的一句话对比
很多人会混淆模版方法和策略模式,其实区分很简单:
| 模版方法模式 | 策略模式 | |
|---|---|---|
| 实现方式 | 继承 | 组合 |
| 变化点 | 算法内部的某些步骤 | 整个算法 |
| 流程控制 | 父类控制 | 调用方控制 |
| 适用场景 | 流程固定,部分步骤可变 | 多种算法可随时切换 |
简单记:模版方法变"步骤",策略模式换"算法"。
总结
模版方法模式是设计模式里最接地气的一个,核心思想只有一句话:
把不变的部分抽到父类,把变化的部分留给子类。
当你发现几处代码流程高度相似、只有个别步骤不同时,就是它登场的时候了。
下次再遇到"复制粘贴改一改"的冲动,不妨停下来问自己:这里是不是可以用模版方法整一整?