1. 策略模式基础与C++实现
策略模式是GoF设计模式中行为型模式的经典代表,它定义了算法族,分别封装起来,让它们之间可以互相替换。这种模式的核心在于将算法的使用与实现分离,使得算法可以独立于使用它的客户端变化。
在C++中实现基础策略模式通常包含三个关键组件:
- Context(上下文):维护对具体策略的引用,通过策略接口与策略对象交互
- Strategy(策略接口):声明所有具体策略通用的操作接口
- ConcreteStrategy(具体策略):实现策略接口的具体算法
cpp复制// 策略接口
class SortingStrategy {
public:
virtual void sort(vector<int>& data) = 0;
virtual ~SortingStrategy() = default;
};
// 具体策略A
class QuickSort : public SortingStrategy {
public:
void sort(vector<int>& data) override {
cout << "Sorting using QuickSort" << endl;
// 快速排序实现...
}
};
// 具体策略B
class MergeSort : public SortingStrategy {
public:
void sort(vector<int>& data) override {
cout << "Sorting using MergeSort" << endl;
// 归并排序实现...
}
};
// 上下文
class Sorter {
unique_ptr<SortingStrategy> strategy;
public:
explicit Sorter(unique_ptr<SortingStrategy> strategy)
: strategy(std::move(strategy)) {}
void setStrategy(unique_ptr<SortingStrategy> newStrategy) {
strategy = std::move(newStrategy);
}
void executeSort(vector<int>& data) {
strategy->sort(data);
}
};
这种基础实现虽然清晰,但在实际工程中往往会遇到各种变体需求。比如当策略对象需要携带状态时,或者策略需要在运行时动态组合时,基础模式就显得不够灵活了。
注意:策略接口的设计是模式的关键,它应该足够抽象以涵盖所有具体策略的共性,但又不能过于宽泛而失去指导意义。在实践中,我通常会先写几个具体策略的实现,再从中提取出公共接口。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 运行时策略组合变体
在实际项目中,我们经常遇到需要组合多个策略的情况。传统做法是创建组合策略类,但这会导致类爆炸问题。通过使用函数对象和标准库组件,我们可以实现更灵活的运行时策略组合。
2.1 基于std::function的策略模式
C++11引入的std::function为策略模式提供了更灵活的实现方式:
cpp复制class TextProcessor {
using Strategy = function<string(const string&)>;
vector<Strategy> strategies;
public:
void addStrategy(Strategy strategy) {
strategies.push_back(move(strategy));
}
string process(const string& text) {
string result = text;
for (const auto& strategy : strategies) {
result = strategy(result);
}
return result;
}
};
// 使用示例
TextProcessor processor;
processor.addStrategy([](const string& s) {
return s + " [processed]";
});
processor.addStrategy([](const string& s) {
return "Prefix: " + s;
});
string result = processor.process("Hello");
// 输出: Prefix: Hello [processed]
这种实现方式允许在运行时动态添加、移除或重新排序策略,比经典实现更加灵活。我在一个文本处理框架中采用这种设计,使得客户可以通过配置文件定义处理流水线,而无需重新编译代码。
2.2 策略组合器模式
对于需要复杂策略组合的场景,可以设计专门的策略组合器:
cpp复制class StrategyCombiner {
vector<unique_ptr<Strategy>> strategies;
public:
void addStrategy(unique_ptr<Strategy> strategy) {
strategies.push_back(move(strategy));
}
Result execute(const Context& ctx) {
Result finalResult;
for (const auto& strategy : strategies) {
PartialResult pr = strategy->execute(ctx);
finalResult.combine(pr);
}
return finalResult;
}
};
这种模式在游戏AI中特别有用,比如可以将移动策略、攻击策略、防御策略等组合起来形成完整的AI行为。我在一个塔防游戏项目中就采用了类似设计,使得每个敌人的行为都可以通过策略组合来定制。
3. 编译时策略选择变体
C++的模板元编程能力允许我们在编译时选择策略,这种零成本抽象对于性能敏感的场景特别有价值。
3.1 基于模板的策略模式
cpp复制template <typename SortingStrategy>
class Sorter {
SortingStrategy strategy;
public:
void sort(vector<int>& data) {
strategy.sort(data);
}
};
struct QuickSortPolicy {
void sort(vector<int>& data) {
// 快速排序实现
}
};
struct MergeSortPolicy {
void sort(vector<int>& data) {
// 归并排序实现
}
};
// 使用示例
Sorter<QuickSortPolicy> quickSorter;
vector<int> data = {...};
quickSorter.sort(data);
这种编译时策略选择的优点是:
- 无虚函数调用开销
- 编译器可以进行更好的优化
- 策略可以访问上下文类的私有成员
我在一个高频交易系统中使用这种模式,将市场数据处理策略作为模板参数,获得了显著的性能提升。
3.2 策略特征模板
对于更复杂的场景,可以使用策略特征模板:
cpp复制template <typename T>
struct SerializationStrategy;
template <>
struct SerializationStrategy<JSONFormat> {
static string serialize(const Data& data) {
// JSON序列化实现
}
};
template <>
struct SerializationStrategy<XMLFormat> {
static string serialize(const Data& data) {
// XML序列化实现
}
};
template <typename Format>
class DataExporter {
public:
string exportData(const Data& data) {
return SerializationStrategy<Format>::serialize(data);
}
};
这种模式在需要为不同类型提供不同策略实现时特别有用。我在一个跨平台序列化库中采用了这种设计,使得可以为每种数据格式提供最优的序列化策略。
4. 状态感知策略变体
传统策略模式通常假设策略是无状态的,但在实际应用中,策略可能需要维护内部状态。这时就需要对基础模式进行扩展。
4.1 有状态策略实现
cpp复制class CachingStrategy {
unordered_map<string, string> cache;
public:
virtual string fetch(const string& key) {
if (cache.count(key)) {
return cache[key];
}
string value = doFetch(key);
cache[key] = value;
return value;
}
protected:
virtual string doFetch(const string& key) = 0;
};
class NetworkFetchStrategy : public CachingStrategy {
protected:
string doFetch(const string& key) override {
// 网络获取实现
}
};
class DatabaseFetchStrategy : public CachingStrategy {
protected:
string doFetch(const string& key) override {
// 数据库查询实现
}
};
这种设计允许策略维护自己的状态,同时仍然保持接口的一致性。我在一个分布式缓存代理中使用了类似的设计,每个策略维护自己的缓存状态,而上下文类不需要关心这些细节。
4.2 策略工厂模式
当策略创建逻辑复杂时,可以引入策略工厂:
cpp复制class StrategyFactory {
public:
unique_ptr<Strategy> createStrategy(const Config& config) {
if (config.type == "A") {
return make_unique<StrategyA>(config.params);
} else if (config.type == "B") {
return make_unique<StrategyB>(config.params);
}
throw invalid_argument("Unknown strategy type");
}
};
工厂模式特别适合以下场景:
- 策略创建需要复杂初始化
- 需要根据配置动态选择策略类型
- 希望隐藏具体策略类的实现细节
我在一个插件系统中采用了这种设计,使得插件可以注册自己的策略工厂,系统则通过统一接口创建和管理策略实例。
5. 现代C++中的策略模式演进
随着C++标准的发展,策略模式也出现了新的实现方式和应用场景。
5.1 基于lambda的策略注入
C++11引入的lambda表达式为策略模式提供了更简洁的实现方式:
cpp复制class TaskScheduler {
using SchedulingStrategy = function<void(vector<Task>&)>;
SchedulingStrategy strategy;
public:
void setStrategy(SchedulingStrategy newStrategy) {
strategy = newStrategy;
}
void schedule(vector<Task>& tasks) {
strategy(tasks);
}
};
// 使用示例
TaskScheduler scheduler;
scheduler.setStrategy([](vector<Task>& tasks) {
// 自定义调度逻辑
});
这种方式的优点是:
- 无需定义单独的策略类
- 策略逻辑可以紧挨着使用它的代码
- 可以捕获上下文变量
我在一个事件处理框架中大量使用这种模式,使得事件处理策略可以直接在事件注册点定义,大大提高了代码的可读性。
5.2 策略模式与并发
在多线程环境下使用策略模式需要注意线程安全问题:
cpp复制class ThreadSafeStrategy {
mutex mtx;
shared_ptr<Strategy> strategy;
public:
void setStrategy(shared_ptr<Strategy> newStrategy) {
lock_guard<mutex> lock(mtx);
strategy = newStrategy;
}
void execute() {
shared_ptr<Strategy> localStrategy;
{
lock_guard<mutex> lock(mtx);
localStrategy = strategy;
}
if (localStrategy) {
localStrategy->execute();
}
}
};
关键注意事项:
- 策略切换需要加锁
- 策略执行时持有策略指针的副本
- 避免在策略执行过程中修改策略
我在一个实时数据处理器中采用了这种线程安全策略模式,确保了在高并发环境下策略可以安全切换和执行。
6. 策略模式在项目中的实际应用
6.1 游戏开发中的AI策略
在游戏AI开发中,策略模式可以优雅地实现行为切换:
cpp复制class EnemyAI {
unique_ptr<MovementStrategy> movementStrategy;
unique_ptr<AttackStrategy> attackStrategy;
public:
void setMovementStrategy(unique_ptr<MovementStrategy> strategy) {
movementStrategy = move(strategy);
}
void setAttackStrategy(unique_ptr<AttackStrategy> strategy) {
attackStrategy = move(strategy);
}
void update(float deltaTime) {
if (movementStrategy) movementStrategy->execute(deltaTime);
if (attackStrategy) attackStrategy->execute(deltaTime);
}
};
// 具体策略示例
class ChaseMovement : public MovementStrategy {
void execute(float deltaTime) override {
// 追逐玩家逻辑
}
};
class PatrolMovement : public MovementStrategy {
void execute(float deltaTime) override {
// 巡逻逻辑
}
};
这种设计使得我们可以为不同的敌人类型配置不同的行为策略,甚至可以在运行时根据游戏状态动态切换策略。我在一个RPG游戏中采用这种设计,实现了高度可配置的敌人AI系统。
6.2 金融系统中的定价策略
在金融系统中,不同的产品可能需要不同的定价策略:
cpp复制class PricingEngine {
unique_ptr<PricingStrategy> strategy;
public:
void setStrategy(unique_ptr<PricingStrategy> newStrategy) {
strategy = move(newStrategy);
}
double calculatePrice(const Trade& trade) {
return strategy->calculate(trade);
}
};
// 具体策略示例
class BlackScholesStrategy : public PricingStrategy {
double calculate(const Trade& trade) override {
// Black-Scholes模型实现
}
};
class MonteCarloStrategy : public PricingStrategy {
double calculate(const Trade& trade) override {
// 蒙特卡洛模拟实现
}
};
这种架构允许系统在不修改核心定价引擎的情况下支持新的定价模型。我在一个期权交易系统中采用这种设计,使得量化分析师可以独立开发和测试新的定价策略。
7. 策略模式的性能考量
策略模式的性能特征因实现方式而异:
| 实现方式 | 虚函数调用 | 内存开销 | 适用场景 |
|---|---|---|---|
| 经典虚函数实现 | 有 | 较高 | 需要运行时多态 |
| std::function实现 | 有 | 中等 | 需要灵活的策略组合 |
| 模板策略实现 | 无 | 低 | 性能敏感,编译时确定策略 |
| CRTP实现 | 无 | 低 | 需要静态多态 |
在性能敏感的场景中,我通常会遵循以下优化原则:
- 如果策略在程序生命周期内不会改变,优先使用模板策略
- 对于频繁调用的策略,确保策略对象小而高效
- 避免在策略接口中使用虚函数+标准容器的组合,这会导致双重间接
- 考虑使用自定义分配器来减少策略对象的内存分配开销
在最近的一个高性能计算项目中,我通过将运行时策略模式改为编译时策略模板,获得了约15%的性能提升。关键是要根据具体场景选择合适的实现变体。
