本文目录导读:

是的,模板方法模式(Template Method Pattern)的核心目的就是定义一个操作中的算法骨架,而将一些具体步骤的实现延迟到子类中。
在 PHP 中,这个模式非常常用,尤其是当多个子类有相同的处理流程,但某些步骤的具体实现不同时。
核心定义(与你的问题直接相关)
模板方法模式通过一个 “模板方法” 来定义算法的骨架。
- 骨架(Skeleton):指的是一个固定的、不可更改的执行顺序。
步骤A -> 步骤B -> 步骤C,这个顺序在父类的模板方法中已经写死。 - 具体步骤:骨架中的某些步骤(通常是步骤A、B或C的具体逻辑)被定义为 抽象方法 或 可重写的方法,由子类来实现。
答案是肯定的:它定义算法骨架,并允许子类在不改变骨架结构的情况下,重新定义骨架中的某些特定步骤。
PHP 示例演示
假设我们要制作一个通用的“数据导出”流程,骨架是固定的:连接数据库 -> 2. 查询数据 -> 3. 格式化数据 -> 4. 输出文件,但连接不同数据库(MySQL vs. PostgreSQL)的方式不同。
<?php
// 1. 抽象类:定义算法骨架
abstract class DataExporter
{
/**
* 模板方法(定义骨架)
* final 关键字防止子类重写整个流程
*/
final public function export(string $filePath): void
{
$connection = $this->connectDatabase(); // 步骤1:连接(子类实现)
$rawData = $this->queryData($connection); // 步骤2:查询(子类实现)
$formattedData = $this->formatData($rawData); // 步骤3:格式化(通用实现,但子类也可覆盖)
$this->outputToFile($formattedData, $filePath); // 步骤4:输出(通用实现)
$this->cleanup($connection); // 钩子方法(可选)
}
// 抽象方法:必须由子类实现
abstract protected function connectDatabase(): object;
abstract protected function queryData(object $connection): array;
// 具体方法:通用实现,子类可以选择覆盖
protected function formatData(array $data): string
{
return json_encode($data); // 默认使用 JSON 格式
}
// 具体方法:通用实现(通常不覆盖)
protected function outputToFile(string $data, string $path): void
{
file_put_contents($path, $data);
echo "文件已导出至: {$path}\n";
}
// 钩子方法:空实现,子类可选覆盖
protected function cleanup(object $connection): void
{
// 默认不做清理
}
}
// 2. 具体子类:实现特定步骤
class MySQLExporter extends DataExporter
{
protected function connectDatabase(): object
{
echo "连接 MySQL 数据库...\n";
return new stdClass(); // 模拟连接对象
}
protected function queryData(object $connection): array
{
echo "使用 MySQL 语法查询数据...\n";
return [['id' => 1, 'name' => 'Alice']];
}
// 覆盖格式化方法
protected function formatData(array $data): string
{
return "MySQL格式: " . implode(', ', array_keys($data[0]));
}
}
class PostgreSQLExporter extends DataExporter
{
protected function connectDatabase(): object
{
echo "连接 PostgreSQL 数据库...\n";
return new stdClass();
}
protected function queryData(object $connection): array
{
echo "使用 PostgreSQL 语法查询数据...\n";
return [['id' => 1, 'name' => 'Bob']];
}
// 使用默认的 JSON 格式化,不覆盖
}
// 3. 客户端使用
function exportAndShow(DataExporter $exporter, string $file): void
{
// 调用模板方法,执行固定的骨架
$exporter->export($file);
}
echo "=== 使用 MySQL 导出 ===\n";
exportAndShow(new MySQLExporter(), '/tmp/mysql_data.txt');
echo "\n=== 使用 PostgreSQL 导出 ===\n";
exportAndShow(new PostgreSQLExporter(), '/tmp/pg_data.txt');
?>
输出效果:
=== 使用 MySQL 导出 ===
连接 MySQL 数据库...
使用 MySQL 语法查询数据...
MySQL格式: id, name
文件已导出至: /tmp/mysql_data.txt
=== 使用 PostgreSQL 导出 ===
连接 PostgreSQL 数据库...
使用 PostgreSQL 语法查询数据...
文件已导出至: /tmp/pg_data.txt
关键点观察:
export()方法(骨架)在父类中定义,且使用final保护,子类无法改变执行顺序(连接 -> 查询 -> 格式化 -> 输出)。connectDatabase()和queryData()是抽象的,子类必须提供不同的实现。formatData()是可选的“钩子”,子类可以覆盖(如 MySQL 覆盖了),也可以不覆盖(如 PostgreSQL 使用默认 JSON)。
模板方法模式的核心特征
| 特征 | 说明 |
|---|---|
| 意图 | 定义一个操作的算法骨架,并将一些步骤延迟到子类中实现。 |
| 关键结构 | 抽象父类:包含 final 的模板方法(骨架)和若干个抽象/具体方法。具体子类:实现父类定义的抽象方法(步骤细节)。 |
| 与策略模式区别 | 模板方法是 继承 层面的复用(固定骨架,子类填空);策略模式是 组合 层面的复用(替换整个算法)。 |
| 适用场景 | 多个类有相同的处理流程,但其中某些步骤的具体逻辑不同(如框架中的初始化、ORM 的保存流程、支付网关的调用流程)。 |
| 优点 | 代码复用(骨架在父类只写一次)。 扩展性好(新增子类只需实现少数方法)。 符合开闭原则(对扩展开放,对修改关闭)。 |
当你需要 固定业务流程 但允许 灵活定制具体步骤 时,模板方法模式就是最佳选择。