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

06_策略模式 Strategy Pattern

1. 基本信息

中文名称: 策略模式

英文名称: Strategy Pattern

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

Rust 中常见实现方式: trait、泛型、dyn Trait、闭包

策略模式的核心是:将可变化的算法或行为封装起来,使调用方依赖抽象而不是具体实现,从而可以在运行时或编译期灵活替换算法或策略。

代码示例


2. 模式核心思想

策略模式的核心思想是:

将算法或行为从调用方分离出来,通过统一接口封装不同实现,调用方无需修改即可选择不同策略。

换句话说,策略模式强调“行为可替换性”和“解耦算法与使用方”。


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

在实际开发中,策略模式主要解决以下问题:

一个功能存在多种实现方式,调用方需要灵活选择。
避免在调用方写大量 if/else 或 match 判断。
算法可能经常变化,需要便于扩展。
保持调用方代码稳定,减少对具体算法的依赖。

例如:

  • 不同排序算法(冒泡、快速、归并)可替换
  • 支付方式(微信、支付宝、银行卡)可动态选择
  • 文件压缩方式(zip、gzip、none)可灵活切换
  • 验证策略(用户名、邮箱、手机号)可以自由组合

策略模式让调用方只依赖抽象接口,而不关心具体实现。


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

如果不使用策略模式,可能出现:

调用方代码中大量 if/else 分支判断算法类型。
算法逻辑和调用方耦合,修改算法影响调用方。
代码扩展性差,每增加一种算法都需要修改调用方。
重复代码多,维护难度增加。

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

在传统 OOP 语言中,策略模式通常有三个角色:

 Strategy 接口:定义算法抽象。
 ConcreteStrategy:具体算法实现。
 Context:持有策略对象,调用策略方法。

调用方通过设置不同策略对象来改变行为,而不修改 Context 内部逻辑。


6. Rust 中的实现思路

6.1 Rust 中通常怎么实现

Rust 中策略模式通常依赖 trait 作为策略接口:

trait Strategy {
    fn execute(&self, data: &mut Vec<i32>);
}

struct BubbleSort;
struct QuickSort;

impl Strategy for BubbleSort {
    fn execute(&self, data: &mut Vec<i32>) { data.sort(); }
}

impl Strategy for QuickSort {
    fn execute(&self, data: &mut Vec<i32>) { data.sort(); }
}

fn run_strategy<S: Strategy>(strategy: S, data: &mut Vec<i32>) {
    strategy.execute(data);
}

调用方可以选择不同的策略:

let mut nums = vec![3, 1, 2];
run_strategy(BubbleSort, &mut nums);

动态分发也可以通过 dyn Strategy

let strategy: Box<dyn Strategy> = Box::new(QuickSort);
strategy.execute(&mut nums);

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

  1. Rust 不依赖类和继承,策略由 trait + impl 实现。
  2. 静态分发(泛型)可在编译期确定策略,性能接近内联函数。
  3. 动态分发(dyn Trait)允许运行时选择策略,但有轻微开销。
  4. 不需要上下文类强耦合,Context 可以是简单函数或结构体。

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

  • 对简单逻辑,可直接使用闭包替代 trait:
let bubble_sort = |data: &mut Vec<i32>| data.sort();
bubble_sort(&mut nums);
  • 对可组合的策略,闭包或迭代器链可以简化实现。
  • Rust 强调编译期安全和零成本抽象,静态分发泛型策略是最自然写法。

7. Rust 中涉及的语言特性

trait
泛型
dyn Trait
impl
闭包(Function-like Strategy)
所有权和借用
结构体作为 Context 可选

这些特性分别解决的问题:

  • trait:行为抽象
  • 泛型:静态分发策略
  • dyn Trait:动态策略选择
  • 所有权和借用:安全管理数据访问

8. 性能与工程代价

  • 静态分发策略几乎零成本,编译器会内联执行。
  • 动态分发策略需要一次虚函数调用,开销略高。
  • 如果策略链复杂,可能导致闭包嵌套较深,需要注意类型复杂性。
  • Rust 的 trait object 和泛型组合让策略模式既安全又高效。

9. 典型应用场景

排序算法选择
支付方式动态选择
文件压缩方式选择
验证或规则策略
数据处理策略(过滤、转换、聚合)

10. 和相似模式的区别

  • 状态模式:状态模式关注对象状态变化后的行为不同;策略模式关注算法或行为本身可替换。
  • 命令模式:命令模式把操作封装成对象,可以延迟执行;策略模式只提供可替换算法接口。
  • 模板方法模式:模板方法固定流程,部分步骤可变;策略模式整个算法可替换。

11. 使用该模式的优点

避免大量 if/else 分支判断
算法可以独立扩展和修改
调用方与具体算法解耦
可组合或动态切换策略
静态分发策略性能高

12. 使用该模式的代价

动态策略需要虚函数调用开销
策略数量过多,可能增加类型复杂性
对简单算法使用可能显得冗余
调试闭包组合策略可能稍复杂

13. 什么时候不应该使用

算法只有一种,没必要抽象
调用方和算法耦合变化不频繁
对性能要求极端,需要避免虚函数
对象行为高度依赖内部状态,可能更适合状态模式

14. 一个简单例子思路

场景: 文件压缩工具

  • 支持 zip、gzip、none 三种压缩方式
  • 调用方只选择策略,无需关心具体实现

处理流程:

定义 Strategy trait
为每种压缩算法实现 trait
调用方持有 trait 对象(泛型或 Box<dyn Strategy>)
执行压缩操作
可以动态替换策略

适合练习 Rust 特性:trait、泛型、dyn Trait、闭包、所有权与借用。


15. 总结一句话

策略模式的本质是:

将可变算法或行为封装起来,调用方依赖抽象而不是具体实现,从而可以在运行时或编译期灵活切换策略。在 Rust 中,trait、泛型和闭包是实现策略模式的核心工具。