模板方法模式
引言
在软件开发中,当多个相似算法具有相同骨架但部分步骤实现不同时(如不同格式的文件解析流程),直接复制代码会导致维护灾难。模板方法模式通过定义算法框架并将可变步骤延迟到子类实现,完美解决了"变与不变"的平衡问题,成为框架设计中复用算法结构的利器。
诞生背景
GoF在《设计模式》中提出模板方法模式,解决三大痛点:
- 代码重复:相似算法在不同类中重复实现核心流程
- 扩展困难:新增算法类型需要重写整个流程
- 流程失控:子类可能意外改变核心算法步骤顺序
典型案例:不同数据库连接流程(Oracle/MySQL连接步骤相同但具体实现不同)
演进过程
- GoF基础(1994):确立抽象类+具体子类的经典结构
- 框架时代:Spring的JdbcTemplate将数据访问流程模板化
- 现代应用:前端框架生命周期钩子(React/Vue组件挂载流程)
- 函数式扩展:Java/C#通过函数接口实现模板方法变体
核心概念
- 抽象类(AbstractClass)
- 定义算法骨架(模板方法)
- 声明抽象操作供子类实现
- 具体子类(ConcreteClass)
- 实现抽象操作
- 不改变算法结构
- 钩子方法(Hook)
- 可选步骤,提供默认实现
- 子类可选择性覆盖
通用实现
Java 实现
java
// 抽象模板类
abstract class DataParser {
// 模板方法(final防止子类覆盖算法结构)
public final void parse() {
openDataSource();
readData();
processData();
closeDataSource();
}
// 抽象步骤(子类必须实现)
protected abstract void readData();
protected abstract void processData();
// 通用实现(所有子类共享)
protected void openDataSource() {
System.out.println("Default: Opening data source");
}
// 钩子方法(子类可选覆盖)
protected void closeDataSource() {
System.out.println("Default: Closing data source");
}
}
// 具体子类:CSV解析器
class CsvParser extends DataParser {
protected void readData() {
System.out.println("Reading CSV data...");
}
protected void processData() {
System.out.println("Processing CSV records...");
}
}
// 具体子类:JSON解析器
class JsonParser extends DataParser {
protected void readData() {
System.out.println("Reading JSON stream...");
}
protected void processData() {
System.out.println("Parsing JSON objects...");
}
// 覆盖钩子方法
protected void closeDataSource() {
System.out.println("Custom: Flushing JSON buffer");
}
}
// 客户端
public class Client {
public static void main(String[] args) {
DataParser csv = new CsvParser();
csv.parse(); // 使用默认关闭逻辑
DataParser json = new JsonParser();
json.parse(); // 使用自定义关闭逻辑
}
}PHP 实现
php
// 抽象模板类
abstract class ReportGenerator {
// 模板方法(final保护算法结构)
final public function generate(): void {
$this->connect();
$this->fetchData();
$this->formatReport();
$this->output();
$this->disconnect();
}
// 抽象步骤(子类实现)
abstract protected function fetchData(): void;
abstract protected function formatReport(): void;
// 通用步骤
protected function connect(): void {
echo "Default: Connecting to database\n";
}
// 钩子方法(可选覆盖)
protected function disconnect(): void {
echo "Default: Disconnecting\n";
}
protected function output(): void {
echo "Default: Outputting report\n";
}
}
// 具体子类:PDF报告
class PdfReportGenerator extends ReportGenerator {
protected function fetchData(): void {
echo "Fetching data for PDF\n";
}
protected function formatReport(): void {
echo "Formatting PDF layout\n";
}
}
// 具体子类:HTML报告
class HtmlReportGenerator extends ReportGenerator {
protected function fetchData(): void {
echo "Fetching data for HTML\n";
}
protected function formatReport(): void {
echo "Generating HTML tags\n";
}
// 覆盖钩子方法
protected function output(): void {
echo "Custom: Rendering HTML in browser\n";
}
}
// 客户端
$pdf = new PdfReportGenerator();
$pdf->generate();
$html = new HtmlReportGenerator();
$html->generate();应用场景
- 框架设计:Spring的JdbcTemplate(连接/执行/关闭流程固定)
- 文档处理:不同格式文件(PDF/DOCX)的生成流程
- 游戏开发:游戏角色的固定行为框架(移动/攻击/死亡)
- 工作流引擎:审批流程的固定阶段(提交/审核/归档)
- 跨平台开发:相同功能在不同平台的实现差异
案例:跨平台编译工具
Java 实现
java
// 抽象编译模板
abstract class Compiler {
public final void compile() {
preprocess();
compileSource();
link();
if (needsOptimization()) {
optimize();
}
generateOutput();
}
abstract void compileSource();
abstract void link();
void preprocess() {
System.out.println("Common: Running preprocessor");
}
// 钩子方法
boolean needsOptimization() {
return false; // 默认不优化
}
void optimize() {
throw new UnsupportedOperationException();
}
abstract void generateOutput();
}
// Windows编译器
class WindowsCompiler extends Compiler {
void compileSource() {
System.out.println("Windows: Compiling with MSVC");
}
void link() {
System.out.println("Windows: Linking .obj files");
}
// 覆盖钩子
boolean needsOptimization() {
return true;
}
void optimize() {
System.out.println("Windows: Running PGO optimization");
}
void generateOutput() {
System.out.println("Windows: Generating .exe file");
}
}
// Linux编译器
class LinuxCompiler extends Compiler {
void compileSource() {
System.out.println("Linux: Compiling with GCC");
}
void link() {
System.out.println("Linux: Linking .o files");
}
void generateOutput() {
System.out.println("Linux: Generating ELF binary");
}
}PHP 实现
php
// 抽象部署模板
abstract class DeploymentPipeline {
final public function deploy(): void {
$this->build();
$this->runTests();
$this->deployToStaging();
if ($this->needsApproval()) {
$this->waitForApproval();
}
$this->deployToProduction();
$this->cleanup();
}
abstract protected function build(): void;
abstract protected function runTests(): void;
protected function deployToStaging(): void {
echo "Common: Deploying to staging\n";
}
// 钩子方法
protected function needsApproval(): bool {
return false;
}
protected function waitForApproval(): void {
throw new Exception("Not implemented");
}
abstract protected function deployToProduction(): void;
protected function cleanup(): void {
echo "Common: Cleaning build artifacts\n";
}
}
// 生产环境部署
class ProductionDeployment extends DeploymentPipeline {
protected function build(): void {
echo "Production: Building with minification\n";
}
protected function runTests(): void {
echo "Production: Running integration tests\n";
}
// 覆盖钩子
protected function needsApproval(): bool {
return true;
}
protected function waitForApproval(): void {
echo "Production: Waiting for manager approval\n";
}
protected function deployToProduction(): void {
echo "Production: Blue-green deployment\n";
}
}
// 开发环境部署
class DevDeployment extends DeploymentPipeline {
protected function build(): void {
echo "Dev: Fast build without optimization\n";
}
protected function runTests(): void {
echo "Dev: Running unit tests only\n";
}
protected function deployToProduction(): void {
echo "Dev: Direct deploy to dev server\n";
}
}优点
- 代码复用:公共算法流程在抽象类中实现
- 反向控制:父类控制流程,子类实现细节(好莱坞原则)
- 扩展性好:新增算法类型只需扩展子类
- 流程标准化:确保核心算法步骤不被篡改
- 减少重复:消除子类中的冗余代码
缺点
- 继承限制:Java/PHP单继承限制扩展能力
- 类膨胀:每个变体都需要创建子类
- 理解成本:算法流程分散在多层类中
- 灵活性差:运行时难以改变算法结构
- 违反LSP风险:子类可能破坏父类约束
扩展
- 策略模式组合:将可变步骤委托给策略对象java
class Context { private StepStrategy strategy; void executeTemplate() { fixedStep1(); strategy.execute(); fixedStep2(); } } - 函数式实现:Java/PHP使用Lambda替代子类php
class TemplateRunner { public function run(callable $customStep) { $this->step1(); $customStep(); $this->step2(); } } - 钩子扩展点:增加更多可选的扩展方法
- 模板方法链:多个模板方法协同工作(如构建管道)
模式协作
- 与工厂方法:模板方法常调用工厂方法创建对象
- 与策略模式:策略封装算法,模板定义流程框架
- 与享元模式:共享模板方法中的不变部分
- 与装饰器模式:装饰器可修改模板方法的某些步骤
延伸思考
- 框架设计哲学:
- Spring的JdbcTemplate封装了JDBC流程(连接/执行/异常处理)
- Laravel的Eloquent模型提供保存流程模板(验证/事件/持久化)
- 生命周期控制:
- Android的Activity生命周期(onCreate/onStart/onResume)
- React的useEffect钩子执行流程(挂载/更新/卸载)
- 函数式编程替代:
java
public void executeTemplate(Runnable step1, Runnable step2) {
fixedSetup();
step1.run();
step2.run();
fixedCleanup();
}- 流程可视化:通过模板方法生成标准化的流程文档
总结
模板方法模式是算法复用的经典解决方案,通过"框架固定,步骤可变"的设计哲学,在抽象类中固化算法骨架,在子类中实现具体步骤。其核心价值在于:
- 流程标准化:确保核心算法步骤顺序
- 复用最大化:消除重复的算法框架代码
- 扩展友好:通过子类化支持新变体
在框架设计、跨平台开发、流程引擎等场景中,模板方法模式能显著提升代码复用性和可维护性,成为架构设计中"不变应万变"的利器。随着函数式编程的发展,其实现方式更加灵活多样,但核心思想仍持续影响着现代框架设计。