设计模式精讲:深入掌握模板方法模式
模式定义
模板方法模式(Template Method)是行为型设计模式,在父类中定义算法框架,允许子类在不改变结构的前提下重写特定步骤。该模式通过固定算法骨架实现代码复用,同时保留扩展灵活性。
核心思想
算法骨架固定化:定义不可变的主流程
步骤差异化实现:开放具体步骤给子类实现
反向控制结构:父类调用子类方法(Hollywood原则)
适用场景
多流程共享相同算法结构
需要规范标准化流程(如审批流程)
存在多个相似子类需要统一约束
框架扩展点设计(如Spring初始化)
模式结构
AbstractClass:定义模板方法和抽象步骤
ConcreteClass:实现具体步骤
Client:调用模板方法执行流程
PHP实现示例:数据导出模板
<?php
// 抽象导出类
abstract class DataExporter {
// 模板方法(final禁止重写)
final public function export(string $filename) {
$this->prepareData();
$formatted = $this->formatData();
$this->writeToOutput($filename, $formatted);
$this->afterExport();
}
// 公共步骤
private function prepareData() {
echo "正在准备基础数据...\n";
}
// 抽象方法(必须实现)
abstract protected function formatData(): string;
// 钩子方法(可选重写)
protected function afterExport() {}
// 公共方法
private function writeToOutput(string $filename, string $content) {
file_put_contents($filename, $content);
echo "已写入文件: $filename\n";
}
}
// CSV导出实现
class CsvExporter extends DataExporter {
protected function formatData(): string {
$data = ["ID,Name", "1,John", "2,Alice"];
return implode("\n", $data);
}
protected function afterExport() {
echo "CSV导出完成通知\n";
}
}
// JSON导出实现
class JsonExporter extends DataExporter {
protected function formatData(): string {
$data = [["id" => 1], ["id" => 2]];
return json_encode($data);
}
}
// 客户端调用
$csvExporter = new CsvExporter();
$csvExporter->export('data.csv');
$jsonExporter = new JsonExporter();
$jsonExporter->export('data.json');
/* 输出:
正在准备基础数据...
已写入文件: data.csv
CSV导出完成通知
正在准备基础数据...
已写入文件: data.json
*/
Go实现示例:HTTP请求处理模板
package main
import (
"fmt"
"net/http"
"time"
)
// 处理流程接口
type HandlerTemplate interface {
Validate(*http.Request) error
Authorize() bool
Process() (interface{}, error)
Log(time.Duration)
}
// 模板执行函数
func ExecuteTemplate(h HandlerTemplate, w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 校验参数
if err := h.Validate(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 权限验证
if !h.Authorize() {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// 执行业务逻辑
res, err := h.Process()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 记录日志
h.Log(time.Since(start))
// 返回响应
fmt.Fprintf(w, "%+v", res)
}
// 具体实现:订单查询
type OrderHandler struct {
UserRole string
OrderID string
}
func (h *OrderHandler) Validate(r *http.Request) error {
h.OrderID = r.URL.Query().Get("order_id")
if h.OrderID == "" {
return fmt.Errorf("missing order_id")
}
return nil
}
func (h *OrderHandler) Authorize() bool {
return h.UserRole == "admin"
}
func (h *OrderHandler) Process() (interface{}, error) {
return map[string]interface{}{
"order_id": h.OrderID,
"amount": 99.9,
}, nil
}
func (h *OrderHandler) Log(d time.Duration) {
fmt.Printf("[%s] 处理订单 %s 耗时: %v\n",
time.Now().Format("2006-01-02 15:04:05"), h.OrderID, d)
}
func main() {
// 模拟HTTP请求
handler := &OrderHandler{UserRole: "admin"}
// 通过模板执行
ExecuteTemplate(handler,
mockResponseWriter{},
&http.Request{URL: mockURL()})
}
// 辅助测试结构
type mockResponseWriter struct{}
func (m mockResponseWriter) Write(p []byte) (n int, err error) {
fmt.Println("响应内容:", string(p))
return len(p), nil
}
func (m mockResponseWriter) Header() http.Header { return make(http.Header) }
func (m mockResponseWriter) WriteHeader(int) {}
func mockURL() *URL {
u, _ := http.NewRequest("GET", "/?order_id=123", nil).URL
return u
}
执行结果:
[2023-08-20 14:30:45] 处理订单 123 耗时: 103.4µs
响应内容: map[amount:99.9 order_id:123]
模式优缺点
✅ 优点:
代码复用最大化
反向控制流程
符合开闭原则
便于维护标准化流程
❌ 缺点:
父类定义限制子类灵活性
继承关系导致强耦合
步骤划分不当会导致模式僵化
不同语言实现差异
模式变体与演进
钩子方法
通过空实现方法提供扩展点(PHP示例中的afterExport)策略组合
Go版本结合策略模式实现灵活替换模板函数
非OOP语言使用高阶函数实现(如JavaScript)流程模板
结合DSL定义业务流程图(如Camunda工作流引擎)
最佳实践建议
合理拆分步骤
每个步骤应保持单一职责,粒度适中谨慎使用继承
Go等语言优先使用组合替代继承明确扩展边界
使用final/非导出标识保护模板方法添加生命周期钩子
通过前置/后置处理增强扩展性单元测试保障
重点验证模板流程的顺序正确性
总结
模板方法模式通过定义算法骨架与可变步骤的分离,实现了流程标准化与实现灵活性的完美平衡。该模式特别适用于需要严格规范流程但允许局部定制的场景,如:
多格式数据导出
统一业务处理流程
框架扩展点设计
复杂操作分解
PHP与Go实现差异的本质源于语言特性:
PHP通过类继承体系实现天然的模式支持
Go借助接口和组合展现更灵活的实践方式
实际应用中需注意:
避免过度抽象导致流程僵化
合理使用钩子方法保持扩展性
在流程稳定性和扩展需求间寻找平衡点
掌握模板方法模式的关键在于理解"固定骨架,可变细节"的设计哲学,这种分层次的抽象思维对构建可维护的大型系统至关重要。