1. 策略模式初探:当if-else开始失控
十年前我刚入职时接手过一个电商促销系统,代码里密密麻麻的if-else像蜘蛛网一样缠绕着每个核心方法。双十一前新增一个满减策略,需要修改至少5个类中的条件判断,测试同事看我的眼神仿佛在看一个罪犯。这种经历让我深刻理解了《设计模式》中那句"发现变化,封装变化"的真谛。
策略模式(Strategy Pattern)本质上是对算法族的封装。就像游戏手柄的ABXY键可以映射不同技能,它允许在运行时动态切换对象的行为。在C++中,我们通过将算法提取到独立的策略类,并让环境类持有策略接口的指针/引用,实现解耦。这个模式特别适合处理:
- 同一问题存在多种解法
- 算法需要频繁切换
- 条件语句过于复杂的场景
2. 模式结构解剖:从UML到C++实现
2.1 经典三要素结构
cpp复制// 策略接口
class SortingStrategy {
public:
virtual void sort(vector<int>& data) = 0;
virtual ~SortingStrategy() = default;
};
// 具体策略
class QuickSort : public SortingStrategy {
public:
void sort(vector<int>& data) override {
cout << "Applying quick sort" << endl;
// 快速排序实现...
}
};
class MergeSort : public SortingStrategy {
//...类似实现
};
// 环境类
class SortContext {
private:
SortingStrategy* strategy;
public:
explicit SortContext(SortingStrategy* s) : strategy(s) {}
void setStrategy(SortingStrategy* s) {
strategy = s;
}
void executeSort(vector<int>& data) {
strategy->sort(data);
}
};
这个基础实现有几个关键点需要注意:
- 策略接口通常声明为抽象类,纯虚函数确保子类必须实现
- 环境类持有策略指针而非具体策略,这是多态的关键
- 策略对象生命周期管理需要特别注意(后面会详细讨论)
2.2 现代C++的进化实现
C++11后的新特性让策略模式实现更优雅:
cpp复制// 使用function代替接口类
using Strategy = std::function<void(vector<int>&)>;
class ModernContext {
Strategy strategy;
public:
explicit ModernContext(Strategy s) : strategy(std::move(s)) {}
void setStrategy(Strategy s) {
strategy = std::move(s);
}
void execute(vector<int>& data) {
strategy(data);
}
};
// 使用lambda注册策略
ModernContext ctx([](vector<int>& data) {
ranges::sort(data); // C++20 ranges
});
这种实现方式省去了继承体系,但牺牲了接口的显式约束。根据项目规范选择合适的形式很重要。
3. 实战案例:电商促销系统改造
3.1 原始代码的典型问题
假设我们有一个传统的促销计算模块:
cpp复制class PriceCalculator {
public:
double calculate(double price, UserType type, PromoType promo) {
if (type == UserType::VIP) {
if (promo == PromoType::Seasonal) {
return price * 0.7;
} else if (...) {
// 更多嵌套判断
}
}
// 其他条件分支...
}
};
这种代码的维护痛点包括:
- 新增促销类型需要修改核心类
- 条件逻辑难以测试
- 无法运行时变更策略
3.2 策略模式重构步骤
- 提取策略接口:
cpp复制class DiscountStrategy {
public:
virtual double apply(double price) = 0;
virtual bool eligible(const OrderContext& ctx) = 0;
virtual ~DiscountStrategy() = default;
};
- 实现具体策略:
cpp复制class SeasonalDiscount : public DiscountStrategy {
public:
double apply(double price) override { return price * 0.7; }
bool eligible(const OrderContext& ctx) override {
return ctx.userType == VIP && ctx.season == "Winter";
}
};
- 重构环境类:
cpp复制class PricingEngine {
private:
vector<unique_ptr<DiscountStrategy>> strategies;
public:
void addStrategy(unique_ptr<DiscountStrategy> s) {
strategies.push_back(std::move(s));
}
double calculateFinalPrice(OrderContext ctx) {
double price = ctx.basePrice;
for (auto& s : strategies) {
if (s->eligible(ctx)) {
price = s->apply(price);
}
}
return price;
}
};
3.3 策略注册的优雅实现
利用工厂模式实现策略的动态注册:
cpp复制class StrategyFactory {
private:
unordered_map<string, function<unique_ptr<DiscountStrategy>()>> creators;
public:
void registerStrategy(string name, auto creator) {
creators[name] = creator;
}
unique_ptr<DiscountStrategy> create(string name) {
if (auto it = creators.find(name); it != creators.end()) {
return it->second();
}
throw invalid_argument("Unknown strategy");
}
};
// 注册示例
StrategyFactory factory;
factory.registerStrategy("seasonal", [] {
return make_unique<SeasonalDiscount>();
});
4. 高级应用与性能优化
4.1 策略无状态化设计
当策略无需维护状态时,可以优化为单例:
cpp复制class StatelessStrategy : public DiscountStrategy {
StatelessStrategy() = default;
public:
static StatelessStrategy& instance() {
static StatelessStrategy singleton;
return singleton;
}
//...实现接口
};
// 使用时
ctx.addStrategy(&StatelessStrategy::instance());
4.2 编译期策略选择
通过模板实现编译期策略绑定:
cpp复制template<typename Strategy>
class CompileTimeCalculator {
Strategy strategy;
public:
double calculate(double price) {
return strategy.apply(price);
}
};
// 使用
CompileTimeCalculator<VIPDiscount> calc;
这种方法牺牲了运行时灵活性,但性能最优。
4.3 策略组合模式
实现策略的AND/OR组合:
cpp复制class CompositeStrategy : public DiscountStrategy {
vector<DiscountStrategy*> strategies;
bool isAnd; // true表示AND组合
public:
//...实现组合逻辑
bool eligible(const OrderContext& ctx) override {
return isAnd ? all_of(...) : any_of(...);
}
};
5. 陷阱与最佳实践
5.1 内存管理要点
- 所有权明确化:
cpp复制// 坏味道:原始指针难以管理生命周期
Context(SortingStrategy* s);
// 好做法:使用智能指针
Context(unique_ptr<SortingStrategy> s);
- 策略共享时的线程安全:
cpp复制class SharedStrategy {
mutable mutex mtx;
public:
void execute() const {
lock_guard<mutex> lock(mtx);
//...
}
};
5.2 测试策略
- 模拟策略测试环境类:
cpp复制TEST(ContextTest, ExecuteStrategy) {
MockStrategy mock;
EXPECT_CALL(mock, execute(_)).Times(1);
Context ctx(&mock);
ctx.run();
}
- 策略的基准测试:
cpp复制static void BM_QuickSort(benchmark::State& state) {
vector<int> data(state.range(0));
QuickSort strategy;
for (auto _ : state) {
strategy.sort(data);
}
}
BENCHMARK(BM_QuickSort)->Range(8, 8<<10);
5.3 设计警示
-
避免策略膨胀:当策略类超过20个时,考虑引入策略分组或元策略
-
警惕过度设计:简单条件判断用策略模式反而增加复杂度
-
接口设计原则:
- 策略接口应保持单一职责
- 参数尽量使用上下文对象而非松散参数
6. 与其他模式的关系
-
与工厂模式配合:
- 工厂创建策略对象
- 环境类使用策略接口
-
与装饰器模式对比:
- 装饰器增强现有行为
- 策略完全替换算法
-
与状态模式异同:
- 状态模式中状态知道下一个状态
- 策略模式中各策略相互独立
在实际项目中,我重构过一个图像处理框架,将各种滤镜算法实现为策略,配合工厂和组合模式,使新增滤镜时间从2天缩短到2小时。关键是要根据具体场景灵活运用这些模式。
