模板方法模式:为什么你的代码重复率这么高?模板方法模式了解一下

作者:互联网

2026-03-24

AI模型库

假设现在你正在开发一个数据导出功能。 支持导出 CSV 和 Excel。

CSV 导出的逻辑:

  • 连接数据库
  • 查询数据
  • 把数据转成逗号分隔字符串 (差异点)
  • 生成文件并下载
  • 记录日志

Excel 导出的逻辑:

  • 连接数据库
  • 查询数据
  • 把数据转成二进制 Excel 格式 (差异点)
  • 生成文件并下载
  • 记录日志

除了第 3 步不一样,其他 4 步完全一样。 如果你写了两个类 ExportCsv 和 ExportExcel,并且把相同代码复制了两遍,那就是**重复代码 (Duplication)**。 如果以后要改“连接数据库”的逻辑,你得改两个地方。

模板方法模式 (Template Method Pattern) 定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。 简而言之:爸爸定规矩,儿子填细节。

一、PHP 8.1+ 实战演示

1. 抽象基类 (Abstract Class)

定义算法的骨架。

<?php

declare(strict_types=1);

namespaceAppPatternsTemplateMethod;

abstractclass DataExporter
{

    // ⚡️ 核心:模板方法
    // 加上 final,防止子类修改流程顺序
    finalpublicfunction export(): void
    {
        $this->connectDB();
        $data = $this->queryData();
        $formatted = $this->formatData($data); // 这一步交给子类实现
        $this->download($formatted);
        $this->log();
    }

    privatefunction connectDB(): void
    {
        echo"? 连接数据库...n";
    }

    privatefunction queryData(): array
    {
        echo"? 查询数据...n";
        return ['User1', 'User2', 'User3'];
    }

    // 抽象方法:强制子类必须实现
    abstractprotectedfunction formatData(array $data): string;

    privatefunction download(string $content): void
    {
        echo"⬇️ 下载文件: " . substr($content, 0, 20) . "...n";
    }

    // 钩子方法 (Hook):子类可以选择性覆盖
    protectedfunction log(): void
    {
        echo"? [默认日志] 导出完成。n";
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

2. 具体子类 (Concrete Class)

只实现差异化的部分。

class CsvExporter extends DataExporter
{
    protectedfunction formatData(array $data): string
    {
        echo"? 正在转换为 CSV 格式...n";
        return implode(',', $data);
    }
}

class ExcelExporter extends DataExporter
{
    protectedfunction formatData(array $data): string
    {
        echo"? 正在转换为 Excel 二进制格式...n";
        return"BINARY_EXCEL_DATA_" . json_encode($data);
    }

    // 覆盖钩子:自定义日志行为
    protectedfunction log(): void
    {
        echo"? [详细日志] Excel 导出耗时 2s。n";
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

3. 客户端调用

客户端只管调用 export(),不需要知道内部的步骤。

echo "--- 导出 CSV ---n";
$csv = new CsvExporter();
$csv->export();

echo"n--- 导出 Excel ---n";
$excel = new ExcelExporter();
$excel->export();

// 输出:
// --- 导出 CSV ---
// ? 连接数据库...
// ? 查询数据...
// ? 正在转换为 CSV 格式...
// ⬇️ 下载文件: User1,User2,User3...
// ? [默认日志] 导出完成。

// --- 导出 Excel ---
// ? 连接数据库...
// ? 查询数据...
// ? 正在转换为 Excel 二进制格式...
// ⬇️ 下载文件: BINARY_EXCEL_DATA_...
// ? [详细日志] Excel 导出耗时 2s。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

二、90% 程序员不知道的细节

“好莱坞原则” (Hollywood Principle):Don't call us, we'll call you.

在模板方法模式中,是父类调用子类的方法($this->formatData()),而不是子类调用父类。 这与通常的继承(子类调用 parent::method())是反过来的。 这种控制反转 (IoC) 是框架设计的核心。比如 PHPUnit,你只需要写 testXXX(),PHPUnit 框架会自动去调用它,而不需要你写 main() 函数来手动调用。

三、、什么时候用?(场景)

一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。

各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。

控制子类扩展:模板方法只在特定点调用“钩子”操作,这样就只允许在这些点进行扩展。

四、总结

模板方法模式 (Template Method Pattern):

  • 一句话:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。
  • 核心价值:代码复用和流程控制。确保所有子类都遵循相同的处理流程,同时允许它们定制特定的步骤。

延伸思考:如果步骤非常多(比如 20 个),而且不同的子类只需要实现其中的 2-3 个,其他的都想用默认实现。这时候该怎么设计?(提示:大量使用钩子方法,父类提供默认的空实现或通用实现,子类按需覆盖。)

相关标签:

AI 大模型 资讯