1. 策略模式:C++行为型设计模式的实战解析
在C++开发中,策略模式(Strategy Pattern)是我最常用的行为型设计模式之一。它通过定义算法族并封装每个算法,使它们可以互相替换,让算法的变化独立于使用算法的客户端。简单来说,就像游戏角色的技能系统 - 角色(Context)可以随时切换不同的攻击技能(ConcreteStrategy),而无需修改角色类本身。
这个模式特别适合以下场景:
- 一个系统需要在多种算法中选择一种
- 需要避免暴露复杂的、与算法相关的数据结构
- 一个类定义了多种行为,并且这些行为在类中以多个条件语句的形式出现
2. 策略模式的核心结构与实现
2.1 UML类图解析
典型的策略模式包含三个核心角色:
- Context(环境类):持有一个Strategy的引用,通过接口与策略交互
- Strategy(抽象策略类):定义所有支持的算法的公共接口
- ConcreteStrategy(具体策略类):实现Strategy接口的具体算法
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 << "Sorting using QuickSort" << endl;
// 实际快速排序实现...
}
};
// 具体策略:归并排序
class MergeSort : public SortingStrategy {
public:
void sort(vector<int>& data) override {
cout << "Sorting using MergeSort" << endl;
// 实际归并排序实现...
}
};
// 上下文类
class Sorter {
private:
SortingStrategy* strategy;
public:
Sorter(SortingStrategy* strategy) : strategy(strategy) {}
void setStrategy(SortingStrategy* newStrategy) {
strategy = newStrategy;
}
void executeSort(vector<int>& data) {
strategy->sort(data);
}
};
2.2 现代C++实现改进
使用C++11及以上版本时,我们可以利用智能指针和lambda表达式使实现更现代化:
cpp复制// 使用std::function替代抽象类
using SortStrategy = std::function<void(vector<int>&)>;
class ModernSorter {
private:
SortStrategy strategy;
public:
explicit ModernSorter(SortStrategy strat) : strategy(std::move(strat)) {}
void setStrategy(SortStrategy newStrat) {
strategy = std::move(newStrat);
}
void execute(vector<int>& data) {
strategy(data);
}
};
// 使用示例
ModernSorter sorter([](vector<int>& data) {
// lambda实现快速排序
sort(data.begin(), data.end());
});
3. 策略模式的深入应用技巧
3.1 策略对象的创建与管理
在实际项目中,策略对象的生命周期管理是个关键问题。我推荐以下几种方式:
-
静态策略:在编译时确定策略
cpp复制BubbleSort bs; Sorter sorter(&bs); // 策略对象生命周期由调用方管理 -
动态策略:运行时切换策略
cpp复制Sorter sorter(new QuickSort()); // 使用智能指针更安全 sorter.setStrategy(new MergeSort()); -
策略工厂:集中管理策略创建
cpp复制class StrategyFactory { public: static unique_ptr<SortingStrategy> create(const string& type) { if (type == "quick") return make_unique<QuickSort>(); if (type == "merge") return make_unique<MergeSort>(); throw invalid_argument("Unknown strategy type"); } };
3.2 策略模式与其他模式的结合
策略模式常与其他模式配合使用:
- 策略+工厂模式:通过工厂创建策略对象
- 策略+模板方法:在模板方法中使用策略作为可变步骤
- 策略+装饰器:动态添加策略的附加功能
cpp复制// 策略与装饰器结合示例
class LoggingStrategy : public SortingStrategy {
SortingStrategy* wrapped;
public:
LoggingStrategy(SortingStrategy* s) : wrapped(s) {}
void sort(vector<int>& data) override {
auto start = chrono::high_resolution_clock::now();
wrapped->sort(data);
auto end = chrono::high_resolution_clock::now();
cout << "Sorting took "
<< chrono::duration_cast<chrono::microseconds>(end-start).count()
<< " μs" << endl;
}
};
4. 实战中的问题与解决方案
4.1 性能考量
策略模式的主要性能开销来自:
- 虚函数调用(传统实现)
- std::function调用(现代实现)
- 策略对象的创建/销毁
优化建议:
-
对于高频调用的策略,考虑模板策略:
cpp复制template<typename Strategy> class TemplateSorter { Strategy strategy; public: void execute(vector<int>& data) { strategy.sort(data); } }; -
使用策略对象池避免重复创建
4.2 设计陷阱与规避
-
策略膨胀问题:当策略类过多时,系统会变得复杂
- 解决方案:使用策略层次结构或组合策略
-
客户端必须了解所有策略:客户端需要知道该用哪个策略
- 解决方案:引入策略工厂或配置机制
-
策略间通信问题:策略有时需要共享数据
- 解决方案:通过Context共享状态或使用中介者模式
4.3 测试策略模式
测试策略模式时要注意:
- 单独测试每个具体策略
- 测试Context与策略的交互
- 测试策略切换的正确性
cpp复制TEST(SorterTest, StrategySwitch) {
vector<int> data = {3,1,2};
QuickSort qs;
MergeSort ms;
Sorter sorter(&qs);
sorter.executeSort(data);
ASSERT_EQ(data, vector<int>{1,2,3});
sorter.setStrategy(&ms);
data = {3,1,2};
sorter.executeSort(data);
ASSERT_EQ(data, vector<int>{1,2,3});
}
5. 实际项目案例:游戏战斗系统
我在一个RPG游戏中应用策略模式实现技能系统:
cpp复制// 技能策略
class Skill {
public:
virtual void execute(Character& caster, Character& target) = 0;
virtual ~Skill() = default;
};
// 具体技能
class Fireball : public Skill {
public:
void execute(Character& caster, Character& target) override {
int damage = caster.magicPower * 1.5;
target.takeDamage(damage);
cout << "Fireball hits for " << damage << " damage!" << endl;
}
};
class Heal : public Skill {
public:
void execute(Character& caster, Character& target) override {
int amount = caster.magicPower * 2;
target.restoreHealth(amount);
cout << "Healed for " << amount << " HP!" << endl;
}
};
// 角色类
class Character {
Skill* currentSkill;
public:
void setSkill(Skill* skill) { currentSkill = skill; }
void useSkill(Character& target) {
if(currentSkill) currentSkill->execute(*this, target);
}
};
这个设计允许:
- 轻松添加新技能(新策略类)
- 角色动态切换技能
- 技能效果独立于角色类
6. C++特定实现技巧
6.1 使用函数对象替代虚函数
C++中可以用函数对象(仿函数)替代传统策略接口:
cpp复制struct QuickSorter {
void operator()(vector<int>& data) const {
sort(data.begin(), data.end());
}
};
template<typename Strategy>
void sortWithStrategy(vector<int>& data, Strategy s) {
s(data);
}
// 使用
vector<int> data = {3,1,2};
sortWithStrategy(data, QuickSorter{});
6.2 策略模式的编译时变体
通过模板实现编译时策略选择:
cpp复制template<typename Strategy>
class CompileTimeSorter {
Strategy strategy;
public:
void sort(vector<int>& data) {
strategy(data);
}
};
// 使用
CompileTimeSorter<QuickSorter> sorter;
sorter.sort(data);
这种方式没有运行时开销,但策略不能在运行时改变。
6.3 使用std::variant实现类型安全策略
C++17引入的std::variant可以安全地处理多种策略类型:
cpp复制using Strategy = std::variant<QuickSort, MergeSort, BubbleSort>;
class VariantSorter {
Strategy strategy;
public:
explicit VariantSorter(Strategy s) : strategy(std::move(s)) {}
void sort(vector<int>& data) {
std::visit([&data](auto&& s) { s.sort(data); }, strategy);
}
};
7. 策略模式的最佳实践
根据我的项目经验,以下是使用策略模式的最佳实践:
-
策略粒度控制:策略应该足够小以保持灵活,但也要足够大以有意义
- 不好的例子:把排序的每个步骤都作为单独策略
- 好的例子:将整个排序算法作为一个策略
-
默认策略:为Context提供合理的默认策略
cpp复制class Sorter { std::unique_ptr<SortingStrategy> strategy = std::make_unique<QuickSort>(); public: // ... 其他成员 ... }; -
策略无状态化:尽可能设计无状态的策略类,可以共享使用
cpp复制// 无状态策略可以静态共享 class QuickSort : public SortingStrategy { public: void sort(vector<int>& data) override { /*...*/ } }; static QuickSort globalQuickSort; // 可共享实例 -
策略组合:通过组合简单策略创建复杂行为
cpp复制class CompositeStrategy : public SortingStrategy { vector<unique_ptr<SortingStrategy>> strategies; public: void addStrategy(unique_ptr<SortingStrategy> strategy) { strategies.push_back(std::move(strategy)); } void sort(vector<int>& data) override { for (auto& s : strategies) { s->sort(data); } } };
8. 策略模式在标准库中的应用
C++标准库中也有策略模式的影子:
-
STL算法:许多算法接受比较策略
cpp复制vector<int> data = {3,1,2}; sort(data.begin(), data.end(), greater<int>()); // greater是策略 -
内存分配器:STL容器允许自定义分配策略
cpp复制vector<int, MyAllocator<int>> customAllocVec; // MyAllocator是策略 -
IO流:流使用locale作为格式化策略
cpp复制cout.imbue(locale("de_DE")); // 设置德国地区格式策略
理解这些应用有助于我们在自己的代码中更好地使用策略模式。
