文章

设计模式精讲:深入掌握模板方法模式

模式定义

模板方法模式(Template Method)是行为型设计模式,在父类中定义算法框架,允许子类在不改变结构的前提下重写特定步骤。该模式通过固定算法骨架实现代码复用,同时保留扩展灵活性。

核心思想

  1. 算法骨架固定化:定义不可变的主流程

  2. 步骤差异化实现:开放具体步骤给子类实现

  3. 反向控制结构:父类调用子类方法(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

Go

抽象定义

抽象类+抽象方法

接口+结构体组合

方法重写

extends+override

接口实现+方法重定义

流程控制

父类模板方法

独立模板函数

扩展方式

继承体系

接口组合


模式变体与演进

  1. 钩子方法
    通过空实现方法提供扩展点(PHP示例中的afterExport)

  2. 策略组合
    Go版本结合策略模式实现灵活替换

  3. 模板函数
    非OOP语言使用高阶函数实现(如JavaScript)

  4. 流程模板
    结合DSL定义业务流程图(如Camunda工作流引擎)


最佳实践建议

  1. 合理拆分步骤
    每个步骤应保持单一职责,粒度适中

  2. 谨慎使用继承
    Go等语言优先使用组合替代继承

  3. 明确扩展边界
    使用final/非导出标识保护模板方法

  4. 添加生命周期钩子
    通过前置/后置处理增强扩展性

  5. 单元测试保障
    重点验证模板流程的顺序正确性


总结

模板方法模式通过定义算法骨架与可变步骤的分离,实现了流程标准化实现灵活性的完美平衡。该模式特别适用于需要严格规范流程但允许局部定制的场景,如:

  • 多格式数据导出

  • 统一业务处理流程

  • 框架扩展点设计

  • 复杂操作分解

PHP与Go实现差异的本质源于语言特性:

  • PHP通过类继承体系实现天然的模式支持

  • Go借助接口和组合展现更灵活的实践方式

实际应用中需注意:

  • 避免过度抽象导致流程僵化

  • 合理使用钩子方法保持扩展性

  • 在流程稳定性和扩展需求间寻找平衡点

掌握模板方法模式的关键在于理解"固定骨架,可变细节"的设计哲学,这种分层次的抽象思维对构建可维护的大型系统至关重要。

License:  CC BY 4.0