Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

09_模板方法模式 Template Method Pattern

1. 基本信息

中文名称: 模板方法模式

英文名称: Template Method Pattern

模式类型: 行为型设计模式

Rust 中常见实现方式: trait 默认方法 + trait 方法调用、结构体 + impl

模板方法模式的核心是:在父类或 trait 中定义算法骨架,将部分步骤留给子类或实现者完成,从而保证整体流程不变,同时允许部分步骤可定制。

在 Rust 中,模板方法模式通常通过 trait 默认方法实现算法骨架,具体步骤由具体实现类型重写。

代码示例


2. 模式核心思想

模板方法模式的核心思想是:

固定算法流程,允许部分步骤延迟实现或由子类/具体类型定制,从而在保持整体流程稳定的情况下灵活扩展。

换句话说:

  • 算法总体顺序不可变
  • 某些步骤可以由不同实现重写
  • 避免重复实现算法流程

3. 这个模式解决什么问题

模板方法模式主要解决以下问题:

1. 一个算法的整体流程固定,但其中某些步骤可能有多种实现方式。
2. 避免在每个具体实现中重复编写整体流程。
3. 保持算法结构一致性,同时允许个性化步骤。
4. 便于代码扩展和维护。

例如:

  • 文件处理流程:读取文件 → 解析 → 处理 → 写入
  • 网络请求流程:建立连接 → 发送请求 → 处理响应 → 关闭连接
  • 游戏回合流程:开始回合 → 玩家行动 → 敌人行动 → 结束回合
  • 数据处理流程:预处理 → 转换 → 验证 → 输出

4. 不使用这个模式会怎样

如果不使用模板方法模式,可能出现:

1. 每个具体实现都重复写整个流程。
2. 算法流程不统一,易出错。
3. 调整流程顺序需要修改多个实现。
4. 代码可维护性和扩展性下降。

5. 传统面向对象中的实现思路

在传统 OOP 中,模板方法模式通常有两个角色:

1. 抽象类(AbstractClass):定义算法骨架,模板方法固定流程,并提供可重写的抽象方法。
2. 具体子类(ConcreteClass):实现或重写抽象步骤。

示意结构:

AbstractClass
 ├── template_method()  // 模板方法,调用各步骤
 ├── step1()            // 可选默认实现
 ├── step2()            // 抽象方法,由子类实现
ConcreteClass
 ├── step2()            // 重写抽象方法

调用方只调用 template_method(),流程固定,步骤可自定义。


6. Rust 中的实现思路

6.1 Rust 中通常怎么实现

Rust 没有类继承体系,模板方法模式通常用 trait 默认方法 实现算法骨架:

trait FileProcessor {
    // 模板方法,固定流程
    fn process(&self) {
        self.read_file();
        self.parse();
        self.handle();
        self.write_file();
    }

    // 可重写或抽象的方法
    fn read_file(&self) { println!("读取默认文件"); }
    fn parse(&self);
    fn handle(&self);
    fn write_file(&self) { println!("写入默认文件"); }
}

// 具体实现
struct CsvProcessor;

impl FileProcessor for CsvProcessor {
    fn parse(&self) { println!("解析 CSV 文件"); }
    fn handle(&self) { println!("处理 CSV 数据"); }
}

fn main() {
    let processor = CsvProcessor;
    processor.process(); // 调用模板方法
}

6.2 和传统 OOP 写法相比有什么不同

  1. Rust 不使用继承体系,而是 trait 默认方法实现算法骨架。
  2. 子类(具体实现类型)通过实现 trait 方法重写部分步骤。
  3. 模板方法本身在 trait 内部定义,调用方直接使用具体对象调用模板方法即可。
  4. 不需要虚函数指针或继承结构,静态分发效率高,类型安全。

6.3 Rust 中是否有更自然的替代写法

  • 对简单流程,直接组合函数或闭包也可以实现模板方法思想。
  • 对可复用算法流程,可用 trait 默认方法 + trait object 提供动态分发。
  • Rust 的组合优先策略可以在不依赖继承的情况下实现算法骨架。

7. Rust 中涉及的语言特性

1. trait 默认方法
2. trait 方法重写
3. impl
4. 结构体
5. 所有权和借用
6. dyn Trait(可选)

这些特性分别解决:

  • trait 默认方法 → 定义固定流程
  • 方法重写 → 定制可变步骤
  • impl → 将具体实现与 trait 绑定
  • 所有权和借用 → 安全管理数据访问

8. 性能与工程代价

  • 静态分发模板方法无额外开销,调用方法会被编译器内联。
  • 动态分发(dyn Trait)需要一次虚函数调用,开销轻微。
  • 模板方法模式结构清晰,便于维护,但 trait 默认方法过多可能增加复杂度。

9. 典型应用场景

1. 文件处理流程(读取 → 解析 → 处理 → 写入)
2. 网络请求处理流程
3. 游戏回合或事件流程
4. 数据处理或 ETL 流程
5. 批量任务处理的固定算法骨架

10. 和相似模式的区别

  • 策略模式:策略模式将整个算法或行为替换;模板方法模式固定算法骨架,只替换部分步骤。
  • 工厂模式:工厂模式负责对象创建;模板方法模式关注算法流程。
  • 命令模式:命令模式封装操作、支持延迟执行;模板方法模式关注固定流程执行。

11. 使用该模式的优点

1. 算法流程统一,保证一致性
2. 子类或实现者只需重写部分步骤
3. 减少重复代码,提升可维护性
4. 可静态或动态分发,类型安全
5. 易于扩展新步骤或算法变体

12. 使用该模式的代价

1. 过度拆分步骤可能导致 trait 方法过多
2. 动态分发有轻微虚函数开销
3. 对简单流程使用可能显得冗余
4. 步骤顺序固定,不适合完全灵活的算法变更

13. 什么时候不应该使用

1. 算法流程简单,无需固定模板
2. 流程步骤可自由组合,不需要统一骨架
3. 调用方不需要多种实现,只需单一实现
4. 动态扩展步骤成本较高或不必要

14. 一个简单例子思路

场景: 文件处理器模板

  • 固定流程:读取 → 解析 → 处理 → 写入
  • 默认方法提供通用实现(读取和写入)
  • CSVProcessor 或 JsonProcessor 重写 parse 和 handle 步骤
  • 调用方直接调用 process() 模板方法

适合练习 Rust 特性:trait 默认方法、方法重写、impl、静态分发、dyn Trait。


15. 总结一句话

模板方法模式的本质是:定义算法骨架,固定流程,允许部分步骤由具体类型定制,从而在保持整体流程稳定的前提下实现灵活扩展。在 Rust 中,trait 默认方法和方法重写是实现模板方法的核心工具。