1. 策略模式基础回顾与核心价值
在C++开发中,策略模式(Strategy Pattern)是我最常使用的行为型设计模式之一。它的核心思想是将算法家族封装成独立的类,使得它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端,我在实际项目中多次验证了它的灵活性。
策略模式包含三个关键角色:
- Context(上下文):维护对具体策略的引用,通过策略接口与策略对象交互
- Strategy(策略接口):定义所有支持算法的公共接口
- ConcreteStrategy(具体策略):实现策略接口的具体算法类
这个模式最吸引我的地方在于它的运行时灵活性。记得去年开发一个电商促销系统时,我们可以在不修改上下文类的情况下,动态切换不同的折扣计算策略。比如在"双十一"期间切换到满减策略,而在平常时段使用折扣券策略。
2. 高级应用场景:策略工厂与动态注册
2.1 策略工厂的实现
在实际工程中,我经常将策略模式与工厂模式结合使用。下面是一个我优化过的策略工厂实现:
cpp复制class StrategyFactory {
public:
using Creator = std::function<std::unique_ptr<Strategy>()>;
static StrategyFactory& instance() {
static StrategyFactory factory;
return factory;
}
void registerStrategy(const std::string& name, Creator creator) {
strategies_[name] = creator;
}
std::unique_ptr<Strategy> create(const std::string& name) {
auto it = strategies_.find(name);
if (it != strategies_.end()) {
return it->second();
}
throw std::runtime_error("Unknown strategy: " + name);
}
private:
std::unordered_map<std::string, Creator> strategies_;
};
这种实现允许我们在运行时动态注册新的策略,而不需要重新编译整个系统。我在一个金融交易系统中使用这种方法,使得业务团队可以独立开发新的交易策略并热加载到运行中的系统。
2.2 自动注册技巧
为了让策略的注册更加自动化,我通常会使用静态变量的技巧:
cpp复制class AutoRegister {
public:
AutoRegister(const std::string& name, StrategyFactory::Creator creator) {
StrategyFactory::instance().registerStrategy(name, creator);
}
};
#define REGISTER_STRATEGY(name, class) \
namespace { \
AutoRegister reg_##class(name, []{ \
return std::make_unique<class>(); \
}); \
}
这样,策略类的实现者只需要在cpp文件中添加一行代码即可完成注册:
cpp复制REGISTER_STRATEGY("FastAlgorithm", FastConcreteStrategy)
3. 性能优化:策略对象的管理
3.1 策略对象的复用
在性能敏感的场景下,频繁创建和销毁策略对象可能带来开销。我常用的优化方法是引入对象池:
cpp复制class StrategyPool {
public:
template<typename T>
std::shared_ptr<T> acquire() {
std::lock_guard<std::mutex> lock(mutex_);
auto& pool = pools_[typeid(T).hash_code()];
if (!pool.empty()) {
auto ptr = std::move(pool.back());
pool.pop_back();
return std::shared_ptr<T>(ptr.release(), [this](T* p) {
release(std::unique_ptr<T>(p));
});
}
return std::shared_ptr<T>(new T(), [this](T* p) {
release(std::unique_ptr<T>(p));
});
}
private:
void release(std::unique_ptr<Strategy> strategy) {
std::lock_guard<std::mutex> lock(mutex_);
pools_[typeid(*strategy).hash_code()].push_back(std::move(strategy));
}
std::unordered_map<size_t, std::vector<std::unique_ptr<Strategy>>> pools_;
std::mutex mutex_;
};
这种实现允许策略对象在使用后被回收再利用,特别适合那些初始化成本高的策略对象。
3.2 无状态策略的优化
对于无状态的策略对象,我们可以进一步优化,使用单例模式:
cpp复制class StatelessStrategy : public Strategy {
public:
static StatelessStrategy& instance() {
static StatelessStrategy instance;
return instance;
}
// 实现策略接口...
private:
StatelessStrategy() = default;
};
这样完全避免了对象的创建和销毁开销,在性能测试中,这种优化能使策略调用的吞吐量提升30%以上。
4. 现代C++特性的应用
4.1 使用std::function作为策略接口
在现代C++项目中,我越来越倾向于使用std::function而不是传统的接口类:
cpp复制class FunctionContext {
public:
using Strategy = std::function<std::string(std::string_view)>;
void setStrategy(Strategy strategy) {
strategy_ = std::move(strategy);
}
void execute(std::string_view input) {
if (strategy_) {
std::cout << strategy_(input) << "\n";
}
}
private:
Strategy strategy_;
};
这种方法减少了类的层级结构,使代码更加简洁。我在一个日志处理系统中使用这种实现,允许客户端直接传入lambda表达式作为策略:
cpp复制context.setStrategy([](std::string_view input) {
return std::string(input) + " processed";
});
4.2 策略模式与模板元编程
对于编译时已知的策略,我们可以使用模板来实现零成本抽象:
cpp复制template<typename Strategy>
class TemplateContext {
public:
void execute(std::string_view input) {
std::cout << Strategy{}(input) << "\n";
}
};
struct UppercaseStrategy {
std::string operator()(std::string_view input) const {
std::string result(input);
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
return result;
}
};
这种实现完全消除了运行时多态的开销,在性能测试中比传统实现快2-3倍。我在一个高频交易系统的核心路径上使用这种技术,效果非常显著。
5. 实际项目中的经验教训
5.1 策略粒度的把控
在早期项目中,我曾犯过将策略划分得过细的错误。例如,为一个简单的字符串处理设计了20多种策略,导致系统难以维护。现在我遵循以下原则:
- 只有当算法确实有独立变化的可能性时,才将其提取为策略
- 策略接口应该保持适度抽象,不要过度设计
- 相似的策略可以考虑使用参数化控制,而不是创建多个类
5.2 上下文信息的传递
策略通常需要访问上下文的一些状态信息。我总结了几种传递方式:
- 构造函数注入:适合策略需要的配置信息
- 方法参数传递:适合每次调用可能变化的上下文数据
- 回调接口:适合策略需要主动查询上下文的情况
在一个图像处理项目中,我使用了混合方法:
cpp复制class ImageProcessingStrategy {
public:
explicit ImageProcessingStrategy(const ProcessingConfig& config)
: config_(config) {}
cv::Mat process(const cv::Mat& input, ContextCallback& ctx) {
// 可以使用config_, input和ctx
}
};
5.3 测试策略的实现
策略模式的一个巨大优势是便于单元测试。我通常会:
- 为每个策略编写独立的测试用例
- 使用Mock上下文来验证策略行为
- 对策略组合进行集成测试
一个典型的测试用例:
cpp复制TEST(ConcreteStrategyTest, ShouldProcessInputCorrectly) {
ConcreteStrategy strategy;
MockContext context;
EXPECT_EQ("expected", strategy.doAlgorithm("input", context));
}
6. 与其他模式的协同应用
6.1 策略与模板方法模式
当一组策略有共同的算法结构时,我会使用模板方法模式来消除重复代码:
cpp复制class TemplateStrategy : public Strategy {
public:
std::string doAlgorithm(std::string_view input) final {
validate(input); // 公共前置处理
auto result = process(input); // 可变部分
log(result); // 公共后置处理
return result;
}
protected:
virtual std::string process(std::string_view input) = 0;
private:
void validate(std::string_view input) { /*...*/ }
void log(const std::string& result) { /*...*/ }
};
6.2 策略与装饰器模式
当需要动态添加策略功能时,装饰器模式是个好选择:
cpp复制class DecoratedStrategy : public Strategy {
public:
explicit DecoratedStrategy(std::unique_ptr<Strategy> strategy)
: wrapped_(std::move(strategy)) {}
std::string doAlgorithm(std::string_view input) override {
auto result = wrapped_->doAlgorithm(input);
return postProcess(result);
}
protected:
virtual std::string postProcess(std::string& input) = 0;
private:
std::unique_ptr<Strategy> wrapped_;
};
这种技术在中间件开发中特别有用,比如为基本策略添加缓存、日志或监控功能。
7. 跨平台开发中的策略模式
在开发跨平台应用时,策略模式能优雅地处理平台差异。我的典型做法是:
- 为每个平台创建特定的策略实现
- 在运行时根据平台自动选择策略
- 使用代理模式隐藏平台细节
cpp复制std::unique_ptr<PlatformStrategy> createPlatformStrategy() {
#if defined(WIN32)
return std::make_unique<WindowsStrategy>();
#elif defined(LINUX)
return std::make_unique<LinuxStrategy>();
#elif defined(MACOS)
return std::make_unique<MacStrategy>();
#endif
}
在一个跨平台渲染引擎中,这种设计让我们可以轻松支持新的平台,而不会影响现有代码。
