Skip to content

模板方法模式

引言

在软件开发中,当多个相似算法具有相同骨架部分步骤实现不同时(如不同格式的文件解析流程),直接复制代码会导致维护灾难。模板方法模式通过定义算法框架并将可变步骤延迟到子类实现,完美解决了"变与不变"的平衡问题,成为框架设计中复用算法结构的利器。

诞生背景

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();
        }
    }
  • 钩子扩展点:增加更多可选的扩展方法
  • 模板方法链:多个模板方法协同工作(如构建管道)

模式协作

  • 与工厂方法:模板方法常调用工厂方法创建对象
  • 与策略模式:策略封装算法,模板定义流程框架
  • 与享元模式:共享模板方法中的不变部分
  • 与装饰器模式:装饰器可修改模板方法的某些步骤

延伸思考

  1. 框架设计哲学
  • Spring的JdbcTemplate封装了JDBC流程(连接/执行/异常处理)
  • Laravel的Eloquent模型提供保存流程模板(验证/事件/持久化)
  1. 生命周期控制
  • Android的Activity生命周期(onCreate/onStart/onResume)
  • React的useEffect钩子执行流程(挂载/更新/卸载)
  1. 函数式编程替代
java
public void executeTemplate(Runnable step1, Runnable step2) {
    fixedSetup();
    step1.run();
    step2.run();
    fixedCleanup();
}
  1. 流程可视化:通过模板方法生成标准化的流程文档

总结

模板方法模式是算法复用的经典解决方案,通过"框架固定,步骤可变"的设计哲学,在抽象类中固化算法骨架,在子类中实现具体步骤。其核心价值在于:

  • 流程标准化:确保核心算法步骤顺序
  • 复用最大化:消除重复的算法框架代码
  • 扩展友好:通过子类化支持新变体

在框架设计、跨平台开发、流程引擎等场景中,模板方法模式能显著提升代码复用性和可维护性,成为架构设计中"不变应万变"的利器。随着函数式编程的发展,其实现方式更加灵活多样,但核心思想仍持续影响着现代框架设计。