设计模式(二十三)行为型:模板方法模式详解
模板方法模式(Template Method Pattern)是 GoF 23 种设计模式中的行为型模式之一,其核心价值在于定义一个操作中的算法骨架,而将一些步骤延迟到子类中实现,使得子类可以在不改变算法结构的前提下重新定义算法的某些特定步骤。它通过"父类控制流程,子类实现细节"的方式,实现了代码复用与行为扩展的完美平衡。模板方法模式是构建框架、标准化流程、实现钩子机制、统一处理逻辑(如数据处理、构建流程、业务审批流)的基石,是"好莱坞原则"(Don't call us, we'll call you)在面向对象设计中的经典体现。
一、详细介绍
模板方法模式解决的是"多个类实现同一算法,算法结构相同但某些步骤的具体实现不同,且需要防止子类改变整体流程"的问题。在传统设计中,每个类可能都实现完整的算法,导致大量重复代码;或者使用条件分支,导致逻辑混乱。模板方法模式通过抽象类定义算法的固定骨架(模板方法),将可变步骤声明为抽象方法或钩子方法,由子类实现。
该模式包含以下核心角色:
- AbstractClass(抽象类) :定义算法的骨架(模板方法),该方法通常是一个
final
方法,防止子类覆盖。它包含:- 模板方法(Template Method):定义算法的步骤顺序,调用原语操作(Primitive Operations)。
- 抽象原语操作(Abstract Primitive Operations) :声明为
abstract
,必须由子类实现,代表算法中可变的步骤。 - 具体原语操作(Concrete Primitive Operations):在抽象类中提供默认实现,子类可选择性覆盖。
- 钩子方法(Hook Methods) :在抽象类中提供空实现或默认实现的
protected
方法,子类可选择性覆盖以"挂钩"到算法流程中,实现条件逻辑或扩展点。
- ConcreteClass(具体子类) :继承
AbstractClass
,实现所有抽象原语操作,并可选择性覆盖具体原语操作或钩子方法,以定制算法的特定行为。
模板方法模式的关键优势:
- 代码复用:算法骨架在父类中定义,避免重复。
- 控制流程:父类控制算法的整体结构,防止子类破坏流程。
- 扩展性:子类通过实现抽象方法或覆盖钩子来扩展行为。
- 符合开闭原则:新增行为通过添加新子类实现,无需修改父类。
- 标准化:强制所有子类遵循相同的算法流程。
与"策略模式"相比,策略模式在运行时 通过组合选择算法,模板方法在编译时 通过继承固定流程;策略更灵活,模板方法更强调流程控制。与"状态模式"相比,状态模式关注状态驱动的行为切换 ,模板方法关注流程中步骤的定制 。与"命令模式"相比,命令封装请求 ,模板方法封装算法结构。
模板方法模式适用于:
- 框架设计(如 Spring MVC 的
AbstractController
)。 - 标准化业务流程(如订单处理、审批流)。
- 构建工具(如编译、打包、部署流程)。
- 数据处理管道(如 ETL 流程)。
- 图形渲染流程。
- 单元测试框架的
setUp
/tearDown
。
二、模板方法模式的UML表示
以下是模板方法模式的标准 UML 类图:
extends extends <<abstract>> AbstractClass +templateMethod() +concreteOperation() +hookMethod() +primitiveOperation1() +primitiveOperation2() ConcreteClassA +primitiveOperation1() +primitiveOperation2() +hookMethod() ConcreteClassB +primitiveOperation1() +primitiveOperation2()
图解说明:
AbstractClass
定义templateMethod()
(通常为final
),它按固定顺序调用primitiveOperation1()
,primitiveOperation2()
,concreteOperation()
,hookMethod()
。primitiveOperation1()
和primitiveOperation2()
是抽象方法,必须由子类实现。concreteOperation()
在父类中有具体实现,子类可覆盖。hookMethod()
是钩子,有默认实现(可能为空),子类可选择性覆盖以插入自定义逻辑。ConcreteClassA
和ConcreteClassB
实现抽象方法,并可选择覆盖其他方法。
三、一个简单的Java程序实例及其UML图
以下是一个跨平台软件构建流程的示例,包含编译、测试、打包、部署步骤,不同平台(Windows, Linux)实现不同。
Java 程序实例
java
// 抽象类:软件构建流程
abstract class SoftwareBuildProcess {
// 模板方法:定义构建流程的骨架 (final 防止子类修改流程)
public final void build() {
System.out.println("🚀 开始构建流程...");
checkoutCode();
compile();
runTests();
// 钩子方法:子类可决定是否打包
if (shouldPackage()) {
packageApplication();
}
// 钩子方法:子类可决定是否部署
if (shouldDeploy()) {
deploy();
}
cleanup();
System.out.println("✅ 构建流程完成。\n");
}
// 具体原语操作:在抽象类中实现,所有子类共享
private void checkoutCode() {
System.out.println(" 🔁 1. 检出代码 (从版本控制系统)");
// 模拟操作
}
// 具体原语操作:提供默认实现,子类可覆盖
protected void cleanup() {
System.out.println(" 🧹 6. 清理临时文件 (默认实现)");
// 默认清理逻辑
}
// 钩子方法:提供默认行为(true/false),子类可覆盖以控制流程
protected boolean shouldPackage() {
return true; // 默认打包
}
protected boolean shouldDeploy() {
return false; // 默认不部署
}
// 抽象原语操作:必须由子类实现
protected abstract void compile();
protected abstract void runTests();
protected abstract void packageApplication();
protected abstract void deploy();
}
// 具体子类:Windows 构建流程
class WindowsBuildProcess extends SoftwareBuildProcess {
@Override
protected void compile() {
System.out.println(" ⚙️ 2. 在 Windows 上编译 (使用 MSVC)");
// 调用 Windows 编译器
}
@Override
protected void runTests() {
System.out.println(" 🧪 3. 运行 Windows 测试套件");
// 执行 Windows 测试
}
@Override
protected void packageApplication() {
System.out.println(" 📦 4. 打包为 Windows 安装程序 (.exe)");
// 生成 .exe 安装包
}
@Override
protected void deploy() {
System.out.println(" 🚀 5. 部署到 Windows 服务器");
// 部署逻辑
}
// 覆盖钩子:Windows 构建默认不部署
@Override
protected boolean shouldDeploy() {
return false;
}
// 覆盖具体方法:Windows 特定的清理
@Override
protected void cleanup() {
System.out.println(" 🧹 6. 清理 Windows 临时文件和 .obj 文件");
// Windows 清理逻辑
}
}
// 具体子类:Linux 构建流程
class LinuxBuildProcess extends SoftwareBuildProcess {
@Override
protected void compile() {
System.out.println(" ⚙️ 2. 在 Linux 上编译 (使用 GCC)");
// 调用 GCC 编译器
}
@Override
protected void runTests() {
System.out.println(" 🧪 3. 运行 Linux 测试套件");
// 执行 Linux 测试
}
@Override
protected void packageApplication() {
System.out.println(" 📦 4. 打包为 Linux 包 (.deb 或 .rpm)");
// 生成 .deb 包
}
@Override
protected void deploy() {
System.out.println(" 🚀 5. 部署到 Linux 服务器 (使用 SSH)");
// 部署逻辑
}
// 覆盖钩子:Linux 构建在 CI 环境中自动部署
@Override
protected boolean shouldDeploy() {
// 可根据环境变量决定
return System.getenv("CI_ENV") != null;
}
// 覆盖钩子:Linux 构建时,如果测试失败则不打包
@Override
protected boolean shouldPackage() {
// 简化:假设测试总是通过
return true;
}
}
// 具体子类:快速构建(跳过测试和打包,仅用于开发)
class QuickBuildProcess extends SoftwareBuildProcess {
@Override
protected void compile() {
System.out.println(" ⚡ 2. 快速编译 (仅编译修改的文件)");
// 快速编译逻辑
}
@Override
protected void runTests() {
System.out.println(" ⚠️ 3. 跳过测试 (开发模式)");
// 不运行测试
}
@Override
protected void packageApplication() {
System.out.println(" ⚠️ 4. 跳过打包 (开发模式)");
// 不打包
}
@Override
protected void deploy() {
System.out.println(" ⚠️ 5. 跳过部署 (开发模式)");
// 不部署
}
// 覆盖钩子:快速构建不打包
@Override
protected boolean shouldPackage() {
return false;
}
// 覆盖钩子:快速构建不部署
@Override
protected boolean shouldDeploy() {
return false;
}
}
// 客户端使用示例
public class TemplateMethodPatternDemo {
public static void main(String[] args) {
System.out.println("🏗️ 跨平台软件构建系统 - 模板方法模式示例\n");
// 构建 Windows 版本
System.out.println("--- 构建 Windows 版本 ---");
SoftwareBuildProcess windowsBuild = new WindowsBuildProcess();
windowsBuild.build();
// 构建 Linux 版本
System.out.println("--- 构建 Linux 版本 ---");
SoftwareBuildProcess linuxBuild = new LinuxBuildProcess();
// 模拟 CI 环境
System.setProperty("CI_ENV", "true");
linuxBuild.build();
// 开发者快速构建
System.out.println("--- 开发者快速构建 ---");
SoftwareBuildProcess quickBuild = new QuickBuildProcess();
quickBuild.build();
}
}
实例对应的UML图(简化版)
extends extends extends <<abstract>> SoftwareBuildProcess +build() -checkoutCode() +cleanup() +shouldPackage() +shouldDeploy() +compile() +runTests() +packageApplication() +deploy() WindowsBuildProcess +compile() +runTests() +packageApplication() +deploy() +cleanup() +shouldDeploy() LinuxBuildProcess +compile() +runTests() +packageApplication() +deploy() +shouldPackage() +shouldDeploy() QuickBuildProcess +compile() +runTests() +packageApplication() +deploy() +shouldPackage() +shouldDeploy()
运行说明:
SoftwareBuildProcess
定义build()
模板方法,固定流程:检出 -> 编译 -> 测试 -> (条件打包) -> (条件部署) -> 清理。compile()
,runTests()
,packageApplication()
,deploy()
是抽象方法,由子类实现。cleanup()
是具体方法,有默认实现,子类可覆盖。shouldPackage()
和shouldDeploy()
是钩子方法,子类可覆盖以控制流程分支。WindowsBuildProcess
,LinuxBuildProcess
,QuickBuildProcess
实现各自平台的细节,并通过覆盖钩子定制流程。
四、总结
特性 | 说明 |
---|---|
核心目的 | 定义算法骨架,延迟步骤实现到子类 |
实现机制 | 抽象类定义模板方法(final),子类实现抽象方法 |
优点 | 代码复用、控制流程、符合开闭原则、标准化流程、支持钩子扩展 |
缺点 | 依赖继承(灵活性低于组合)、类爆炸(过多子类)、父类改动影响所有子类 |
适用场景 | 框架设计、标准化流程、构建脚本、数据处理管道、业务审批流 |
不适用场景 | 流程不固定、需要运行时动态组合行为、避免继承的场景 |
模板方法模式使用建议:
- 模板方法通常声明为
final
,防止子类破坏流程。 - 钩子方法是强大的扩展点,用于条件逻辑或可选步骤。
- 可结合"工厂方法模式"创建具体子类。
- 在 Java 中,
Comparator
的compare()
可视为一种函数式模板方法。
架构师洞见:
模板方法模式是"框架设计"的灵魂。在现代架构中,其思想已演变为框架与库的根本区别 :库是"你调用我",框架是"我调用你"(好莱坞原则)。Spring 框架的
JdbcTemplate
,RestTemplate
是其典型应用;JUnit 的@Before
,@After
是钩子方法;Servlet 的doGet()
,doPost()
是模板方法的变体;构建工具(Maven, Gradle)的生命周期是模板方法的宏观体现。未来趋势是:模板方法将与函数式编程 融合,模板方法接受函数式接口作为步骤(如 Java 8 的
Consumer
,Function
);在低代码/无代码平台 中,可视化流程设计器生成模板方法代码;在AI 工作流 中,AI Agent 的"规划-执行-反思"循环可建模为模板方法;在量子软件中,量子算法的通用步骤(初始化、操作、测量)可定义为模板。掌握模板方法模式,是设计可复用框架、标准化系统 的核心能力。作为架构师,应在设计任何需要"统一流程、定制细节"的模块时,优先考虑模板方法模式。它不仅是模式,更是系统秩序的基石------它用不变的骨架约束变化的细节,用父类的权威保障流程的正确,用子类的自由激发实现的创新,从而构建出既稳定又灵活、既统一又多样的软件生态系统。