模板方法模式(Template Method)
前言
模板方法模式定义算法骨架,将某些步骤延迟到子类实现。它让子类在不改变算法结构的情况下重写特定步骤,是代码复用的经典模式。本文将详细讲解模板方法模式的核心原理、钩子方法及实际应用。
一、核心概念
1.1 定义
定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
1.2 核心角色
| 角色 | 说明 |
|---|---|
| 抽象类(AbstractClass) | 定义模板方法和抽象方法 |
| 具体类(ConcreteClass) | 实现抽象方法,完成具体步骤 |
| 模板方法 | 定义算法骨架,通常为 final |
| 钩子方法 | 可选覆盖的方法,提供默认实现 |
二、代码实现
2.1 基础实现
php
<?php
// 抽象类:定义算法骨架
abstract class AbstractClass
{
// 模板方法:定义算法步骤,final 防止子类覆盖
final public function templateMethod(): void
{
$this->baseOperation1();
$this->requiredOperations(); // 子类必须实现
$this->baseOperation2();
$this->hook(); // 子类可选重写
}
// 基础方法:已实现,子类共享
protected function baseOperation1(): void
{
echo "AbstractClass: Base Operation 1" . PHP_EOL;
}
protected function baseOperation2(): void
{
echo "AbstractClass: Base Operation 2" . PHP_EOL;
}
// 抽象方法:子类必须实现
abstract protected function requiredOperations(): void;
// 钩子方法:子类可选重写
protected function hook(): void {}
}
// 具体类 A
class ConcreteClassA extends AbstractClass
{
protected function requiredOperations(): void
{
echo "ConcreteClassA: Required Operation" . PHP_EOL;
}
}
// 具体类 B:重写钩子方法
class ConcreteClassB extends AbstractClass
{
protected function requiredOperations(): void
{
echo "ConcreteClassB: Required Operation" . PHP_EOL;
}
protected function hook(): void
{
echo "ConcreteClassB: Hook Override" . PHP_EOL;
}
}
// 使用
$a = new ConcreteClassA();
$a->templateMethod();
// AbstractClass: Base Operation 1
// ConcreteClassA: Required Operation
// AbstractClass: Base Operation 2
$b = new ConcreteClassB();
$b->templateMethod();
// AbstractClass: Base Operation 1
// ConcreteClassB: Required Operation
// AbstractClass: Base Operation 2
// ConcreteClassB: Hook Override2.2 实际案例:数据导出
php
<?php
// 数据导出抽象类
abstract class DataExporter
{
// 模板方法:导出流程
final public function export(array $data): void
{
$formatted = $this->formatData($data); // 格式化数据
$content = $this->generateContent($formatted); // 生成内容
$result = $this->save($content); // 保存
echo "Export completed: {$result}" . PHP_EOL;
}
// 通用方法:数据格式化
protected function formatData(array $data): array
{
echo "Formatting data..." . PHP_EOL;
return array_map(function ($item) {
$item['exportedAt'] = date('c');
return $item;
}, $data);
}
// 抽象方法:生成内容(子类实现)
abstract protected function generateContent(array $data): string;
// 通用方法:保存文件
protected function save(string $content): string
{
$filename = "export_" . time() . "." . $this->getFileExtension();
echo "Saving to {$filename}..." . PHP_EOL;
return $filename;
}
// 抽象方法:文件扩展名(子类实现)
abstract protected function getFileExtension(): string;
}
// CSV 导出器
class CSVExporter extends DataExporter
{
protected function generateContent(array $data): string
{
$headers = implode(",", array_keys($data[0]));
$rows = array_map(fn($item) => implode(",", array_values($item)), $data);
return $headers . "\n" . implode("\n", $rows);
}
protected function getFileExtension(): string
{
return "csv";
}
}
// JSON 导出器
class JSONExporter extends DataExporter
{
protected function generateContent(array $data): string
{
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
protected function getFileExtension(): string
{
return "json";
}
}
// 使用
$data = [
['name' => 'Flynn', 'age' => 30],
['name' => 'Alice', 'age' => 25],
];
(new CSVExporter())->export($data);
// Formatting data...
// Saving to export_1234567890.csv...
// Export completed: export_1234567890.csv
(new JSONExporter())->export($data);2.3 实际案例:Web 请求处理
php
<?php
// 请求处理抽象类
abstract class RequestHandler
{
// 模板方法:请求处理流程
final public function handle(array $req): array
{
// 1. 验证
if (!$this->validate($req)) {
return ['code' => 400, 'message' => 'Invalid request'];
}
// 2. 鉴权
if (!$this->authenticate($req)) {
return ['code' => 401, 'message' => 'Unauthorized'];
}
// 3. 前置处理(钩子)
$this->beforeProcess($req);
// 4. 核心业务
$result = $this->process($req);
// 5. 后置处理(钩子)
$this->afterProcess($req, $result);
return ['code' => 200, 'data' => $result];
}
// 通用方法:基础验证
protected function validate(array $req): bool
{
return !empty($req);
}
// 通用方法:基础鉴权
protected function authenticate(array $req): bool
{
return isset($req['token']);
}
// 钩子:前置处理
protected function beforeProcess(array $req): void {}
// 抽象方法:核心业务
abstract protected function process(array $req): array;
// 钩子:后置处理
protected function afterProcess(array $req, array $result): void {}
}
// 用户注册处理器
class RegisterHandler extends RequestHandler
{
protected function process(array $req): array
{
echo "Registering user: {$req['username']}" . PHP_EOL;
return ['userId' => time()];
}
protected function afterProcess(array $req, array $result): void
{
echo "Sending welcome email to {$req['username']}" . PHP_EOL;
}
}
// 使用
$handler = new RegisterHandler();
$result = $handler->handle(['token' => 'abc', 'username' => 'Flynn']);
print_r($result);三、适用场景
| 场景 | 说明 |
|---|---|
| 算法骨架固定 | 流程不变,步骤可变 |
| 代码复用 | 多个子类共享公共逻辑 |
| 框架设计 | 提供扩展点供使用者覆盖 |
| 构建/部署流程 | 步骤固定,细节可变 |
| API 请求处理 | 验证→鉴权→处理→后置 |
四、优缺点分析
| 优点 | 缺点 |
|---|---|
| 复用公共代码 | 限制子类灵活性 |
| 固定算法结构 | 可能导致类层次过深 |
| 符合开闭原则(扩展点) | 修改骨架影响所有子类 |
| 钩子方法灵活控制 | 继承耦合度高 |
| 统一流程控制 | 子类可能违反预期 |
五、常见踩坑与问题排查
5.1 子类修改模板方法
php
<?php
// 问题:子类重写了模板方法,破坏流程
class BadClass extends AbstractClass
{
public function templateMethod(): void
{
// 破坏了父类定义的流程
}
}
// 解决:模板方法用 final 修饰
abstract class AbstractClass
{
final public function templateMethod(): void
{
// ... 流程固定,子类无法覆盖
}
}5.2 钩子方法过多
php
<?php
// 问题:钩子方法太多,流程不清晰
class ComplexTemplate
{
public function template(): void
{
$this->hook1();
$this->hook2();
$this->hook3();
$this->hook4();
// 难以理解执行流程
}
}
// 解决:精简钩子,只在关键节点提供扩展六、优化方案与进阶
6.1 结合策略模式
php
<?php
// 模板方法定义骨架,策略模式处理可变部分
abstract class DataProcessor
{
abstract protected function getStrategy(): Strategy;
protected function process(array $data): array
{
$strategy = $this->getStrategy();
// 假设策略返回值是数组
return [$strategy->execute(array_map(fn($x) => (float)$x, $data))];
}
}6.2 函数式模板
php
<?php
// 使用高阶函数替代继承
function createPipeline(array $steps): callable
{
return function ($input) use ($steps) {
return array_reduce(
$steps,
fn($data, $step) => $step($data),
$input
);
};
}
$pipeline = createPipeline([
function ($data) { echo "Step 1" . PHP_EOL; return $data; },
function ($data) { echo "Step 2" . PHP_EOL; return $data; },
function ($data) { echo "Step 3" . PHP_EOL; return $data; },
]);
$pipeline([]);七、全文总结
模板方法模式的核心是 定义算法骨架,将可变步骤延迟到子类实现。
核心要点:
- 模板方法定义固定流程,子类只实现可变部分
- 钩子方法提供可选扩展点,增加灵活性
- 适用于流程固定、步骤可变的场景
- 注意模板方法不应被子类覆盖
- 可与策略模式、函数式编程结合,减少继承耦合
