在软件工程中,重复是魔鬼,但"结构化的重复"却是复用的基石。当我们面对多个业务流程高度相似、仅细节不同的场景时------如不同支付渠道的交易流程、各类报表的生成逻辑、多种数据库的连接操作------如何在不牺牲灵活性的前提下消除冗余?模板方法模式(Template Method Pattern) 给出了优雅的答案。
它通过在抽象父类中固化算法主干,在子类中实现可变步骤,实现了"流程统一、行为可插拔"的设计目标。本文将从理论到实战,层层深入,带你全面掌握这一经典行为型模式。
🧩 第一章:什么是模板方法模式?------封装不变,开放可变
GoF 定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重新定义该算法的某些特定步骤。
模板方法模式的核心在于 "分离不变与可变":
- 不变部分:算法的整体执行顺序(如 初始化 → 处理 → 清理);
- 可变部分:每个步骤的具体实现(如 如何初始化、如何处理数据)。
这种设计体现了 开闭原则(OCP):对扩展开放(新增子类),对修改关闭(无需改动父类流程)。
例如,所有数据库操作都遵循"获取连接 → 执行 SQL → 关闭连接"的流程,但 MySQL、PostgreSQL 的驱动加载方式不同。若将流程写死在每个 DAO 中,将导致大量重复代码;而使用模板方法,只需让不同数据库实现各自的"获取连接"逻辑即可。
💡 关键洞察:模板方法不是为了"避免写代码",而是为了"避免写重复且易错的代码"。
🏗️ 第二章:角色、结构与 UML 图解
模板方法模式包含三个核心角色:
-
AbstractClass(抽象类)
- 定义 模板方法(templateMethod):通常为 `public final`,防止被重写;
- 声明 抽象方法(abstract methods):由子类强制实现;
- 可提供 具体方法(concrete methods):公共逻辑;
- 可定义 钩子方法(hook methods):带默认实现,子类可选重写。
-
ConcreteClass(具体子类)
- 实现所有抽象方法;
- 可选择性重写钩子方法以干预流程。
-
Client(客户端)
- 面向抽象编程,调用模板方法,不感知具体子类。
UML 类图
AbstractClass +final templateMethod() #abstract primitiveOperation1() #abstract primitiveOperation2() #hook() +concreteOperation() ConcreteClassA #primitiveOperation1() #primitiveOperation2() #hook() ConcreteClassB #primitiveOperation1() #primitiveOperation2()
🔒 重要约定:模板方法必须为 `final`,否则子类可能破坏算法骨架,导致不可预知行为。
☕ 第三章:经典示例 ------ 制作饮料的完整实现与扩展
我们以《Head First 设计模式》中的"制作饮料"为例,展示模板方法的完整实现。
java
// 抽象模板类
public abstract class CaffeineBeverage {
// 模板方法:定义不可变流程
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) { // 钩子控制是否加料
addCondiments();
}
}
// 公共具体方法
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
// 抽象方法:子类必须实现
protected abstract void brew();
protected abstract void addCondiments();
// 钩子方法:默认返回 true,子类可覆盖
protected boolean customerWantsCondiments() {
return true;
}
}
// 咖啡实现
public class Coffee extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk");
}
}
// 茶实现(支持用户交互)
public class Tea extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon");
}
@Override
protected boolean customerWantsCondiments() {
String answer = getUserInput();
return answer.toLowerCase().startsWith("y");
}
private String getUserInput() {
try (Scanner scanner = new Scanner(System.in)) {
System.out.print("Would you like lemon with your tea (y/n)? ");
return scanner.nextLine();
}
}
}
运行效果:
- 咖啡自动加糖奶;
- 茶会询问用户是否加柠檬。
✅ 此例完美展示了:流程统一、细节各异、扩展可控。
🎣 第四章:钩子方法(Hook Methods)------灵活干预流程的关键
钩子方法是模板方法模式的"安全阀",它允许子类在不破坏主流程的前提下,有条件地干预执行路径。
钩子的典型用途:
| 场景 | 钩子方法示例 |
|---|---|
| 条件跳过某步骤 | `shouldValidateInput()` |
| 日志开关 | `isDebugEnabled()` |
| 生命周期回调 | `beforeExecute()`, `afterCleanup()` |
| 用户确认 | `userConfirmsAction()` |
设计建议:
- 钩子应有清晰语义 和默认行为(通常为空或返回 true/false);
- 避免在钩子中执行核心业务逻辑;
- 在模板方法中明确标注钩子调用点,便于子类理解。
🌰 示例:在部署流程中,`shouldRunTests()` 钩子可让某些环境跳过测试阶段。
java
public final void deploy() {
build();
if (shouldRunTests()) {
runTests();
}
uploadArtifact();
}
protected boolean shouldRunTests() { return true; } // 默认运行
⚖️ 第五章:模板方法 vs 策略模式 ------ 继承与组合的哲学之争
二者都解决"算法变化"问题,但路径截然不同:
| 维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 实现机制 | 继承(Inheritance) | 组合(Composition) |
| 变化粒度 | 步骤级(细粒度) | 整体算法(粗粒度) |
| 绑定时机 | 编译期(静态) | 运行期(动态) |
| 扩展方式 | 新增子类 | 新增策略类 + 注入 |
| 代码复用 | 通过父类共享骨架 | 通过接口共享行为 |
| 违反原则 | 可能违反"组合优于继承" | 符合开闭原则 |
如何选择?
- 选模板方法 :当流程结构稳定,且你希望强制子类遵循协议(如测试框架、构建流程);
- 选策略模式 :当需要运行时动态切换行为(如支付方式、排序算法)。
💡 实践中常结合使用:模板方法中嵌入策略对象,兼顾流程约束与算法灵活性。
🌱 第六章:Spring 框架中的深度应用 ------ JdbcTemplate 源码剖析
Spring 的 `JdbcTemplate` 是模板方法模式的工业级典范。
核心设计:
- 模板方法:`query()`, `update()`, `execute()`
- 不变部分:获取连接、异常转换、资源关闭
- 可变部分:SQL 语句、参数绑定、结果映射(通过回调传入)
java
// 用户代码
List<User> users = jdbcTemplate.query(
"SELECT id, name FROM users WHERE age > ?",
new Object[]{18},
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))
);
底层简化版实现:
java
public <T> List<T> query(String sql, Object[] args, RowMapper<T> mapper) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
stmt.setObject(i + 1, args[i]);
}
rs = stmt.executeQuery();
List<T> results = new ArrayList<>();
int rowNum = 0;
while (rs.next()) {
results.add(mapper.mapRow(rs, rowNum++)); // 回调:可变部分
}
return results;
} catch (SQLException e) {
throw new DataAccessException("Query failed", e);
} finally {
// 关闭资源(不变部分)
closeQuietly(rs); closeQuietly(stmt); closeQuietly(conn);
}
}
✅ 优势:开发者只需关注 SQL 和映射逻辑,无需处理 JDBC 繁琐样板代码。
🧪 第七章:JUnit 测试生命周期 ------ 自动化测试的骨架
JUnit 4 的 `TestCase` 类(虽已过时,但思想延续)是模板方法的经典应用:
java
public abstract class TestCase {
public void run() {
setUp(); // 钩子:准备测试环境
try {
runTest(); // 执行具体测试方法(通过反射)
} finally {
tearDown(); // 钩子:清理资源
}
}
protected void setUp() {} // 默认空实现
protected void tearDown() {} // 默认空实现
}
JUnit 5 虽改用注解(`@BeforeEach`, `@AfterEach`),但底层仍由测试引擎按固定流程调用这些方法------本质仍是模板方法思想。
✅ 价值:确保每个测试方法都在干净、一致的环境中执行,避免状态污染。
🛠️ 第八章:CI/CD 与构建系统中的模板流程
在 DevOps 领域,几乎所有 CI/CD 工具都隐式使用模板方法:
通用构建流程:
- 拉取代码(Git checkout)
- 安装依赖(npm install / mvn dependency:resolve)
- 运行测试(pytest / mvn test)
- 构建产物(webpack / docker build)
- 部署(kubectl apply / scp)
不同项目仅在"安装依赖"和"构建产物"步骤不同。因此,可设计:
java
public abstract class BuildPipeline {
public final void execute() {
checkoutCode();
installDependencies();
runTests();
buildArtifact();
deploy();
}
protected void checkoutCode() {
System.out.println("git clone ...");
}
protected void runTests() {
System.out.println("Running unit tests...");
}
// 子类必须实现
protected abstract void installDependencies();
protected abstract void buildArtifact();
protected abstract void deploy();
}
前端项目、Java 服务、Python 脚本各自继承并实现差异步骤,实现构建逻辑复用与标准化。
🎬 第九章:好莱坞原则与控制反转(IoC)
模板方法是 "好莱坞原则(Hollywood Principle)" 的直接体现:
"Don't call us, we'll call you."
- 子类不主动调用父类;
- 父类在模板方法中主动回调子类实现的方法;
- 控制权从子类转移到父类 → 控制反转(Inversion of Control, IoC)。
这与 Spring 的依赖注入(DI)共同构成 IoC 的两大实现方式:
- 模板方法:通过继承实现 IoC;
- 依赖注入:通过组合实现 IoC。
💡 架构启示:好的框架应"掌控主流程,开放扩展点",而非让用户拼凑碎片。
🔁 第十章:函数式编程下的演进 ------ 回调与 Lambda 替代继承
在 Java 8+ 或 Kotlin/Scala 中,常用 Lambda 表达式替代继承实现类似效果:
java
public class ProcessTemplate {
public void process(Runnable step1, Runnable step2) {
System.out.println("=== Start ===");
step1.run();
step2.run();
System.out.println("=== End ===");
}
}
// 使用
new ProcessTemplate().process(
() -> System.out.println("Custom Step A"),
() -> System.out.println("Custom Step B")
);
对比分析:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 继承(模板方法) | 强类型、步骤约束、文档清晰 | 类爆炸、编译期绑定 |
| 回调(Lambda) | 灵活、轻量、运行时组合 | 无步骤约束、易出错 |
✅ 建议:复杂流程用模板方法,简单定制用回调。
📊 第十一章:性能、适用场景与局限性深度分析
✅ 最佳适用场景:
- 多个类共享相同算法结构;
- 需要强制子类遵循协议(如框架 API);
- 步骤之间存在强顺序依赖;
- 不希望客户端了解流程细节。
⚠️ 局限性:
- 继承耦合:子类与父类紧耦合,难以替换;
- 单继承限制:Java 不支持多继承,若子类已有父类则无法使用;
- 流程僵化:无法动态调整步骤顺序;
- 测试困难:抽象类难以直接实例化测试。
性能:
- 无反射、无代理,纯方法调用,性能极高;
- 适合高频执行路径(如日志处理、数据解析)。
⚠️ 第十二章:常见误区与重构建议
❌ 误区 1:模板方法未设为 final
→ 子类重写后破坏流程完整性。
✅ 修复:
java
public final void templateMethod() { ... }
❌ 误区 2:抽象方法过多,职责不清
→ 子类实现负担重,违反单一职责。
✅ 修复:合并相关步骤,或引入策略对象。
❌ 误区 3:钩子方法滥用,流程碎片化
→ 主干逻辑被钩子割裂,难以阅读。
✅ 修复:仅在必要干预点设置钩子,并文档化其作用。
🔧 重构建议:
- 若仅有 1--2 个可变点 → 改用策略模式 + 组合;
- 若需多维度变化 → 结合工厂方法或建造者模式。
🤝 第十三章:与其他设计模式的协同作战
模板方法常作为"骨架",与其他模式配合:
| 协同模式 | 作用 | 示例 |
|---|---|---|
| 工厂方法 | 创建流程中所需的对象 | `createDataSource()` 返回不同 DB 连接 |
| 策略模式 | 替换某一步的具体算法 | `getSortStrategy()` 返回不同排序器 |
| 装饰器模式 | 在步骤前后添加增强 | 日志装饰器包装 `brew()` 方法 |
| 观察者模式 | 在关键节点通知监听器 | `onStepCompleted()` 触发事件 |
综合示例:
java
abstract class ReportGenerator {
public final void generate() {
DataSource ds = createDataSource(); // 工厂方法
DataProcessor processor = getProcessor(); // 策略模式
List<Data> data = processor.process(ds);
output(data);
notifyObservers("Report generated"); // 观察者
}
protected abstract DataSource createDataSource();
protected abstract DataProcessor getProcessor();
protected abstract void output(List<Data> data);
protected abstract void notifyObservers(String event);
}
📌 第十四章:最佳实践与架构启示
🛠️ 编码规范:
- 模板方法必须为 `public final`;
- 抽象方法命名以 `doXxx` 开头(如 `doFetchData`),表明其为内部步骤;
- 钩子方法命名清晰(如 `shouldRetry`, `isCacheEnabled`);
- 在 Javadoc 中描述流程顺序。
🧠 架构思维:
- 框架设计者:用模板方法定义扩展点,掌控主流程;
- 应用开发者:通过继承实现业务差异,遵守框架契约;
- 避免过度设计:仅当存在 2 个以上相似流程时才引入。
🌍 现实世界类比:
- 菜谱:步骤固定(洗 → 切 → 炒),食材可变;
- 法律程序:流程法定(立案 → 审理 → 判决),案情各异;
- 电影剧本:三幕结构固定,角色故事不同。
🎯 结语:在约束中创造自由
模板方法模式教会我们一个深刻的工程哲学:真正的灵活性,来自于合理的约束。
它不是限制创新,而是通过定义清晰的边界和协议,让开发者在安全的轨道上高效奔跑。从一杯咖啡的制作,到亿级流量的交易系统,模板方法始终默默支撑着软件世界的秩序与复用。
下次当你面对重复流程时,请记住:
"不要复制粘贴,要提取模板。"
因为,优秀的代码,从来不是写出来的,而是设计出来的 。> 在软件工程中,重复是魔鬼,但"结构化的重复"却是复用的基石。当我们面对多个业务流程高度相似、仅细节不同的场景时------如不同支付渠道的交易流程、各类报表的生成逻辑、多种数据库的连接操作------如何在不牺牲灵活性的前提下消除冗余?模板方法模式(Template Method Pattern) 给出了优雅的答案。
它通过在抽象父类中固化算法主干,在子类中实现可变步骤,实现了"流程统一、行为可插拔"的设计目标。本文将从理论到实战,层层深入,带你全面掌握这一经典行为型模式。
🧩 第一章:什么是模板方法模式?------封装不变,开放可变
GoF 定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重新定义该算法的某些特定步骤。
模板方法模式的核心在于 "分离不变与可变":
- 不变部分:算法的整体执行顺序(如 初始化 → 处理 → 清理);
- 可变部分:每个步骤的具体实现(如 如何初始化、如何处理数据)。
这种设计体现了 开闭原则(OCP):对扩展开放(新增子类),对修改关闭(无需改动父类流程)。
例如,所有数据库操作都遵循"获取连接 → 执行 SQL → 关闭连接"的流程,但 MySQL、PostgreSQL 的驱动加载方式不同。若将流程写死在每个 DAO 中,将导致大量重复代码;而使用模板方法,只需让不同数据库实现各自的"获取连接"逻辑即可。
💡 关键洞察:模板方法不是为了"避免写代码",而是为了"避免写重复且易错的代码"。
🏗️ 第二章:角色、结构与 UML 图解
模板方法模式包含三个核心角色:
-
AbstractClass(抽象类)
- 定义 模板方法(templateMethod):通常为 `public final`,防止被重写;
- 声明 抽象方法(abstract methods):由子类强制实现;
- 可提供 具体方法(concrete methods):公共逻辑;
- 可定义 钩子方法(hook methods):带默认实现,子类可选重写。
-
ConcreteClass(具体子类)
- 实现所有抽象方法;
- 可选择性重写钩子方法以干预流程。
-
Client(客户端)
- 面向抽象编程,调用模板方法,不感知具体子类。
UML 类图
AbstractClass +final templateMethod() #abstract primitiveOperation1() #abstract primitiveOperation2() #hook() +concreteOperation() ConcreteClassA #primitiveOperation1() #primitiveOperation2() #hook() ConcreteClassB #primitiveOperation1() #primitiveOperation2()
🔒 重要约定:模板方法必须为 `final`,否则子类可能破坏算法骨架,导致不可预知行为。
☕ 第三章:经典示例 ------ 制作饮料的完整实现与扩展
我们以《Head First 设计模式》中的"制作饮料"为例,展示模板方法的完整实现。
java
// 抽象模板类
public abstract class CaffeineBeverage {
// 模板方法:定义不可变流程
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) { // 钩子控制是否加料
addCondiments();
}
}
// 公共具体方法
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
// 抽象方法:子类必须实现
protected abstract void brew();
protected abstract void addCondiments();
// 钩子方法:默认返回 true,子类可覆盖
protected boolean customerWantsCondiments() {
return true;
}
}
// 咖啡实现
public class Coffee extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk");
}
}
// 茶实现(支持用户交互)
public class Tea extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon");
}
@Override
protected boolean customerWantsCondiments() {
String answer = getUserInput();
return answer.toLowerCase().startsWith("y");
}
private String getUserInput() {
try (Scanner scanner = new Scanner(System.in)) {
System.out.print("Would you like lemon with your tea (y/n)? ");
return scanner.nextLine();
}
}
}
运行效果:
- 咖啡自动加糖奶;
- 茶会询问用户是否加柠檬。
✅ 此例完美展示了:流程统一、细节各异、扩展可控。
🎣 第四章:钩子方法(Hook Methods)------灵活干预流程的关键
钩子方法是模板方法模式的"安全阀",它允许子类在不破坏主流程的前提下,有条件地干预执行路径。
钩子的典型用途:
| 场景 | 钩子方法示例 |
|---|---|
| 条件跳过某步骤 | `shouldValidateInput()` |
| 日志开关 | `isDebugEnabled()` |
| 生命周期回调 | `beforeExecute()`, `afterCleanup()` |
| 用户确认 | `userConfirmsAction()` |
设计建议:
- 钩子应有清晰语义 和默认行为(通常为空或返回 true/false);
- 避免在钩子中执行核心业务逻辑;
- 在模板方法中明确标注钩子调用点,便于子类理解。
🌰 示例:在部署流程中,`shouldRunTests()` 钩子可让某些环境跳过测试阶段。
java
public final void deploy() {
build();
if (shouldRunTests()) {
runTests();
}
uploadArtifact();
}
protected boolean shouldRunTests() { return true; } // 默认运行
⚖️ 第五章:模板方法 vs 策略模式 ------ 继承与组合的哲学之争
二者都解决"算法变化"问题,但路径截然不同:
| 维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 实现机制 | 继承(Inheritance) | 组合(Composition) |
| 变化粒度 | 步骤级(细粒度) | 整体算法(粗粒度) |
| 绑定时机 | 编译期(静态) | 运行期(动态) |
| 扩展方式 | 新增子类 | 新增策略类 + 注入 |
| 代码复用 | 通过父类共享骨架 | 通过接口共享行为 |
| 违反原则 | 可能违反"组合优于继承" | 符合开闭原则 |
如何选择?
- 选模板方法 :当流程结构稳定,且你希望强制子类遵循协议(如测试框架、构建流程);
- 选策略模式 :当需要运行时动态切换行为(如支付方式、排序算法)。
💡 实践中常结合使用:模板方法中嵌入策略对象,兼顾流程约束与算法灵活性。
🌱 第六章:Spring 框架中的深度应用 ------ JdbcTemplate 源码剖析
Spring 的 `JdbcTemplate` 是模板方法模式的工业级典范。
核心设计:
- 模板方法:`query()`, `update()`, `execute()`
- 不变部分:获取连接、异常转换、资源关闭
- 可变部分:SQL 语句、参数绑定、结果映射(通过回调传入)
java
// 用户代码
List<User> users = jdbcTemplate.query(
"SELECT id, name FROM users WHERE age > ?",
new Object[]{18},
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))
);
底层简化版实现:
java
public <T> List<T> query(String sql, Object[] args, RowMapper<T> mapper) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
stmt.setObject(i + 1, args[i]);
}
rs = stmt.executeQuery();
List<T> results = new ArrayList<>();
int rowNum = 0;
while (rs.next()) {
results.add(mapper.mapRow(rs, rowNum++)); // 回调:可变部分
}
return results;
} catch (SQLException e) {
throw new DataAccessException("Query failed", e);
} finally {
// 关闭资源(不变部分)
closeQuietly(rs); closeQuietly(stmt); closeQuietly(conn);
}
}
✅ 优势:开发者只需关注 SQL 和映射逻辑,无需处理 JDBC 繁琐样板代码。
🧪 第七章:JUnit 测试生命周期 ------ 自动化测试的骨架
JUnit 4 的 `TestCase` 类(虽已过时,但思想延续)是模板方法的经典应用:
java
public abstract class TestCase {
public void run() {
setUp(); // 钩子:准备测试环境
try {
runTest(); // 执行具体测试方法(通过反射)
} finally {
tearDown(); // 钩子:清理资源
}
}
protected void setUp() {} // 默认空实现
protected void tearDown() {} // 默认空实现
}
JUnit 5 虽改用注解(`@BeforeEach`, `@AfterEach`),但底层仍由测试引擎按固定流程调用这些方法------本质仍是模板方法思想。
✅ 价值:确保每个测试方法都在干净、一致的环境中执行,避免状态污染。
🛠️ 第八章:CI/CD 与构建系统中的模板流程
在 DevOps 领域,几乎所有 CI/CD 工具都隐式使用模板方法:
通用构建流程:
- 拉取代码(Git checkout)
- 安装依赖(npm install / mvn dependency:resolve)
- 运行测试(pytest / mvn test)
- 构建产物(webpack / docker build)
- 部署(kubectl apply / scp)
不同项目仅在"安装依赖"和"构建产物"步骤不同。因此,可设计:
java
public abstract class BuildPipeline {
public final void execute() {
checkoutCode();
installDependencies();
runTests();
buildArtifact();
deploy();
}
protected void checkoutCode() {
System.out.println("git clone ...");
}
protected void runTests() {
System.out.println("Running unit tests...");
}
// 子类必须实现
protected abstract void installDependencies();
protected abstract void buildArtifact();
protected abstract void deploy();
}
前端项目、Java 服务、Python 脚本各自继承并实现差异步骤,实现构建逻辑复用与标准化。
🎬 第九章:好莱坞原则与控制反转(IoC)
模板方法是 "好莱坞原则(Hollywood Principle)" 的直接体现:
"Don't call us, we'll call you."
- 子类不主动调用父类;
- 父类在模板方法中主动回调子类实现的方法;
- 控制权从子类转移到父类 → 控制反转(Inversion of Control, IoC)。
这与 Spring 的依赖注入(DI)共同构成 IoC 的两大实现方式:
- 模板方法:通过继承实现 IoC;
- 依赖注入:通过组合实现 IoC。
💡 架构启示:好的框架应"掌控主流程,开放扩展点",而非让用户拼凑碎片。
🔁 第十章:函数式编程下的演进 ------ 回调与 Lambda 替代继承
在 Java 8+ 或 Kotlin/Scala 中,常用 Lambda 表达式替代继承实现类似效果:
java
public class ProcessTemplate {
public void process(Runnable step1, Runnable step2) {
System.out.println("=== Start ===");
step1.run();
step2.run();
System.out.println("=== End ===");
}
}
// 使用
new ProcessTemplate().process(
() -> System.out.println("Custom Step A"),
() -> System.out.println("Custom Step B")
);
对比分析:
| 方式 | 优点 | 缺点 |
|---|---|---|
| 继承(模板方法) | 强类型、步骤约束、文档清晰 | 类爆炸、编译期绑定 |
| 回调(Lambda) | 灵活、轻量、运行时组合 | 无步骤约束、易出错 |
✅ 建议:复杂流程用模板方法,简单定制用回调。
📊 第十一章:性能、适用场景与局限性深度分析
✅ 最佳适用场景:
- 多个类共享相同算法结构;
- 需要强制子类遵循协议(如框架 API);
- 步骤之间存在强顺序依赖;
- 不希望客户端了解流程细节。
⚠️ 局限性:
- 继承耦合:子类与父类紧耦合,难以替换;
- 单继承限制:Java 不支持多继承,若子类已有父类则无法使用;
- 流程僵化:无法动态调整步骤顺序;
- 测试困难:抽象类难以直接实例化测试。
性能:
- 无反射、无代理,纯方法调用,性能极高;
- 适合高频执行路径(如日志处理、数据解析)。
⚠️ 第十二章:常见误区与重构建议
❌ 误区 1:模板方法未设为 final
→ 子类重写后破坏流程完整性。
✅ 修复:
java
public final void templateMethod() { ... }
❌ 误区 2:抽象方法过多,职责不清
→ 子类实现负担重,违反单一职责。
✅ 修复:合并相关步骤,或引入策略对象。
❌ 误区 3:钩子方法滥用,流程碎片化
→ 主干逻辑被钩子割裂,难以阅读。
✅ 修复:仅在必要干预点设置钩子,并文档化其作用。
🔧 重构建议:
- 若仅有 1--2 个可变点 → 改用策略模式 + 组合;
- 若需多维度变化 → 结合工厂方法或建造者模式。
🤝 第十三章:与其他设计模式的协同作战
模板方法常作为"骨架",与其他模式配合:
| 协同模式 | 作用 | 示例 |
|---|---|---|
| 工厂方法 | 创建流程中所需的对象 | `createDataSource()` 返回不同 DB 连接 |
| 策略模式 | 替换某一步的具体算法 | `getSortStrategy()` 返回不同排序器 |
| 装饰器模式 | 在步骤前后添加增强 | 日志装饰器包装 `brew()` 方法 |
| 观察者模式 | 在关键节点通知监听器 | `onStepCompleted()` 触发事件 |
综合示例:
java
abstract class ReportGenerator {
public final void generate() {
DataSource ds = createDataSource(); // 工厂方法
DataProcessor processor = getProcessor(); // 策略模式
List<Data> data = processor.process(ds);
output(data);
notifyObservers("Report generated"); // 观察者
}
protected abstract DataSource createDataSource();
protected abstract DataProcessor getProcessor();
protected abstract void output(List<Data> data);
protected abstract void notifyObservers(String event);
}
📌 第十四章:最佳实践与架构启示
🛠️ 编码规范:
- 模板方法必须为 `public final`;
- 抽象方法命名以 `doXxx` 开头(如 `doFetchData`),表明其为内部步骤;
- 钩子方法命名清晰(如 `shouldRetry`, `isCacheEnabled`);
- 在 Javadoc 中描述流程顺序。
🧠 架构思维:
- 框架设计者:用模板方法定义扩展点,掌控主流程;
- 应用开发者:通过继承实现业务差异,遵守框架契约;
- 避免过度设计:仅当存在 2 个以上相似流程时才引入。
🌍 现实世界类比:
- 菜谱:步骤固定(洗 → 切 → 炒),食材可变;
- 法律程序:流程法定(立案 → 审理 → 判决),案情各异;
- 电影剧本:三幕结构固定,角色故事不同。
🎯 结语:在约束中创造自由
模板方法模式教会我们一个深刻的工程哲学:真正的灵活性,来自于合理的约束。
它不是限制创新,而是通过定义清晰的边界和协议,让开发者在安全的轨道上高效奔跑。从一杯咖啡的制作,到亿级流量的交易系统,模板方法始终默默支撑着软件世界的秩序与复用。
下次当你面对重复流程时,请记住:
"不要复制粘贴,要提取模板。"
因为,优秀的代码,从来不是写出来的,而是设计出来的。