PHP 模板方法模式(Template Method Pattern)
核心概念
模板方法模式定义了一个算法的骨架,将某些步骤延迟到子类实现,子类可以重新定义算法的特定步骤,而不改变算法的结构。

基础骨架实现
1 抽象类(骨架定义)
<?php
declare(strict_types=1);
/**
* 抽象咖啡因饮料类 - 模板方法模式的骨架
*/
abstract class CaffeineBeverage
{
/**
* 模板方法 - 定义算法骨架(final防止子类重写)
*/
final public function prepareRecipe(): void
{
$this->boilWater();
$this->brew(); // 由子类实现
$this->pourInCup();
if ($this->customerWantsCondiments()) { // 钩子方法
$this->addCondiments(); // 由子类实现
}
}
/**
* 公共步骤 - 在抽象类中实现
*/
protected function boilWater(): void
{
echo " boiling water...\n";
}
protected function pourInCup(): void
{
echo " pouring into cup...\n";
}
/**
* 抽象方法 - 必须由子类实现
*/
abstract protected function brew(): void;
abstract protected function addCondiments(): void;
/**
* 钩子方法 - 默认实现,子类可覆盖
*/
protected function customerWantsCondiments(): bool
{
return true; // 默认需要调料
}
}
2 具体子类实现
<?php
declare(strict_types=1);
/**
* 茶类 - 具体实现
*/
class Tea extends CaffeineBeverage
{
protected function brew(): void
{
echo " steeping tea bag...\n";
}
protected function addCondiments(): void
{
echo " adding lemon...\n";
}
// 覆盖钩子方法
protected function customerWantsCondiments(): bool
{
$answer = readline("Would you like lemon with your tea? (y/n): ");
return strtolower($answer[0] ?? 'y') === 'y';
}
}
/**
* 咖啡类 - 具体实现
*/
class Coffee extends CaffeineBeverage
{
protected function brew(): void
{
echo " dripping coffee through filter...\n";
}
protected function addCondiments(): void
{
echo " adding sugar and milk...\n";
}
protected function customerWantsCondiments(): bool
{
$answer = readline("Would you like sugar and milk with your coffee? (y/n): ");
return strtolower($answer[0] ?? 'y') === 'y';
}
}
3 客户端使用
<?php
// 客户端代码
function makeDrink(CaffeineBeverage $beverage): void
{
echo "\n--- Preparing " . get_class($beverage) . " ---\n";
$beverage->prepareRecipe();
}
// 测试
$tea = new Tea();
$coffee = new Coffee();
makeDrink($tea);
makeDrink($coffee);
扩展骨架:订单处理示例
<?php
declare(strict_types=1);
/**
* 订单处理骨架
*/
abstract class OrderProcessTemplate
{
final public function processOrder(bool $isGift): void
{
$this->selectItems();
$this->makePayment();
$this->doDelivery();
if ($isGift) {
$this->wrapGift(); // 钩子
}
$this->sendConfirmation();
}
// 公共算法步骤
protected function selectItems(): void
{
echo " selecting items from cart...\n";
}
protected function makePayment(): void
{
echo " processing payment...\n";
}
protected function sendConfirmation(): void
{
echo " sending confirmation email...\n";
}
// 抽象方法
abstract protected function doDelivery(): void;
// 钩子方法
protected function wrapGift(): void
{
echo " wrapping gift...\n";
}
}
/**
* 在线订单
*/
class OnlineOrder extends OrderProcessTemplate
{
protected function doDelivery(): void
{
echo " scheduling courier pickup...\n";
}
protected function wrapGift(): void
{
echo " adding gift box and card...\n";
}
}
/**
* 店内自提订单
*/
class StorePickup extends OrderProcessTemplate
{
protected function doDelivery(): void
{
echo " preparing items for in-store pickup...\n";
}
// 不覆盖wrapGift,使用默认行为
}
骨架的键组成部分详解
| 组件 | 类型 | 作用 |
|---|---|---|
| 模板方法 | final public | 定义算法骨架,调用各个步骤 |
| 抽象方法 | abstract | 必须由子类实现的具体步骤 |
| 具体方法 | protected/public | 算法中不变的部分,基类实现 |
| 钩子方法 | protected | 可选步骤,默认实现或空实现 |
模板方法 vs 策略模式
| 比较项 | 模板方法 | 策略模式 |
|---|---|---|
| 复用级别 | 类级别(继承) | 对象级别(组合) |
| 控制权 | 父类控制算法结构 | 客户端控制策略对象 |
| 扩展方式 | 子类覆盖步骤 | 实现新策略类 |
| 代码结构 | 继承框架 | 委托/组合 |
实战建议
✅ 应该在以下场景使用:
- 算法有固定步骤,但部分步骤因对象而异
- 多个类有共同的算法逻辑,需要提取公共部分
- 需要子类扩展特定步骤,但不允许改变算法结构
⚠️ 注意事项:
- 模板方法使用
final防止子类破坏算法结构 - 钩子方法应默认提供合理行为,减少子类负担
- 避免模板方法过于庞大,保持步骤单一职责
- 考虑抽象方法数量,过多的抽象方法会增加子类负担
完整示例:数据迁移骨架
<?php
declare(strict_types=1);
/**
* 数据库迁移模板
*/
abstract class DatabaseMigrator
{
final public function migrate(): void
{
$this->beginTransaction();
try {
$this->runMigrations();
$this->updateVersionHistory();
$this->commitTransaction();
if ($this->shouldOutputLog()) {
$this->logSuccess();
}
} catch (Throwable $e) {
$this->rollbackTransaction();
$this->logFailure($e);
throw $e;
}
}
protected function beginTransaction(): void
{
echo " starting transaction...\n";
}
protected function commitTransaction(): void
{
echo " committing transaction...\n";
}
protected function rollbackTransaction(): void
{
echo " rollback transaction...\n";
}
protected function logSuccess(): void
{
echo " migration succeeded.\n";
}
protected function logFailure(Throwable $e): void
{
echo " migration failed: " . $e->getMessage() . "\n";
}
// 钩子
protected function shouldOutputLog(): bool
{
return true;
}
abstract protected function runMigrations(): void;
abstract protected function updateVersionHistory(): void;
}
/**
* MySQL 迁移器
*/
class MySQLMigrator extends DatabaseMigrator
{
protected function runMigrations(): void
{
echo " running MySQL specific SQL migrations...\n";
}
protected function updateVersionHistory(): void
{
echo " updating migration_history table (MySQL)...\n";
}
}
关键要点总结
- 骨架保护:模板方法设为
final,子类不可覆盖 - 抽象分离:将变化的部分抽象为方法,由子类实现
- 钩子灵活性:钩子方法提供可选扩展点,子类可选择性覆盖
- 依赖倒置:子类实现细节,父类定义流程(好莱坞原则)
- 注意边界:过多步骤或过多抽象方法会使类层次难以维护
模板方法模式非常适合代码复用和框架设计场景,尤其是在需要固定流程又有扩展需求时,能有效避免代码重复,同时保证算法结构的稳定性。