PHP模板方法模式骨架

wen PHP项目 3

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

核心概念

模板方法模式定义了一个算法的骨架,将某些步骤延迟到子类实现,子类可以重新定义算法的特定步骤,而不改变算法的结构。

PHP模板方法模式骨架

基础骨架实现

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";
    }
}

关键要点总结

  1. 骨架保护:模板方法设为 final,子类不可覆盖
  2. 抽象分离:将变化的部分抽象为方法,由子类实现
  3. 钩子灵活性:钩子方法提供可选扩展点,子类可选择性覆盖
  4. 依赖倒置:子类实现细节,父类定义流程(好莱坞原则)
  5. 注意边界:过多步骤或过多抽象方法会使类层次难以维护

模板方法模式非常适合代码复用框架设计场景,尤其是在需要固定流程又有扩展需求时,能有效避免代码重复,同时保证算法结构的稳定性。

抱歉,评论功能暂时关闭!