策略模式把一组可替换的算法放到统一接口后面,由持有策略的对象在运行时选择行为。原笔记以“米、毫米两种任务单位”为例;这里保留该场景,并明确策略的所有权、空值处理和单位换算。
对象各自负责什么
跳转到“对象各自负责什么”| 对象 | 职责 |
|---|---|
TaskStrategy | 约定输入如何转换成统一的米单位 |
MetricTaskStrategy | 输入已是米,保持数值 |
MillimeterTaskStrategy | 输入为毫米,除以 1000 |
Task | 独占一个有效策略,执行转换,允许替换策略 |
策略类型选择输入的解释方式,不能只更改显示后缀。1000 毫米应转换成 1 米,再交给使用统一单位的后续算法。
可独立编译的实现
跳转到“可独立编译的实现”// C++17#include <cassert>#include <memory>#include <stdexcept>#include <utility>
class TaskStrategy {public: virtual ~TaskStrategy() = default; virtual double toMeters(double value) const = 0;};
class MetricTaskStrategy final : public TaskStrategy {public: double toMeters(double value) const override { return value; }};
class MillimeterTaskStrategy final : public TaskStrategy {public: double toMeters(double value) const override { return value / 1000.0; }};
class Task {public: explicit Task(std::unique_ptr<TaskStrategy> strategy) { setStrategy(std::move(strategy)); } Task(const Task &) = delete; Task &operator=(const Task &) = delete; Task(Task &&) = delete; Task &operator=(Task &&) = delete;
void setStrategy(std::unique_ptr<TaskStrategy> strategy) { if (!strategy) throw std::invalid_argument("strategy is required"); strategy_ = std::move(strategy); }
double run(double value) const { return strategy_->toMeters(value); }
private: std::unique_ptr<TaskStrategy> strategy_;};
int main() { Task task(std::make_unique<MetricTaskStrategy>()); assert(task.run(2.0) == 2.0); task.setStrategy(std::make_unique<MillimeterTaskStrategy>()); assert(task.run(1000.0) == 1.0);
bool rejected = false; try { task.setStrategy(nullptr); } catch (const std::invalid_argument &) { rejected = true; } assert(rejected); assert(task.run(1000.0) == 1.0); // 无效替换没有破坏原策略}构造时必须提供有效策略,调用 run() 不依赖“调用者应该记得先 set”。替换时先检查新策略,再转移所有权,所以空策略被拒绝时旧策略仍可使用。
所有权与设计边界
跳转到“所有权与设计边界”unique_ptr 让 Task 独占策略;替换策略或 Task 析构时,旧策略会自动释放。基类的虚析构函数保证经 TaskStrategy* 销毁具体策略时行为正确。此例显式禁止复制和移动 Task,避免产生仍可调用 run() 的空策略对象;若业务需要移动,应另外定义移后状态及其可用操作。unique_ptr、虚析构规则
如果策略由外部长期共享,也可选择非拥有引用或其他明确的管理方式,但应写清策略必须活多久。未初始化的裸指针既没有空值保护,也没有这样的寿命契约。
run() 与 setStrategy() 不能在不同线程无保护地同时调用;本例没有线程同步。跨线程共享时应另外定义并发访问规则。
只有简单单位换算时,一个明确的单位枚举和转换函数通常已经足够。策略模式适合需要扩展多套处理算法、算法有独立状态,或确实要在运行时替换行为的情况;它不要求为每一个常量都创建类。