1. 策略模式:何时该用与何时不该用
在C++开发中,策略模式(Strategy Pattern)是我最常使用的设计模式之一。它定义了算法族,分别封装起来,让它们之间可以互相替换。这种模式让算法的变化独立于使用算法的客户。
我第一次真正理解策略模式的价值是在开发一个电商促销系统时。系统需要支持满减、折扣、赠品等多种促销方式,而且这些促销策略会频繁变更。如果采用传统的if-else硬编码方式,代码很快就会变成一团乱麻。策略模式完美解决了这个问题——每种促销策略都是一个独立类,可以随时添加新策略而不影响现有代码。
但策略模式并非万能药。在以下场景中特别适用:
- 一个系统需要在多种算法中选择一种
- 需要避免暴露复杂的、与算法相关的数据结构
- 算法使用频率较高,需要封装以便复用
而在这些情况下则应避免使用:
- 算法非常简单且几乎不会变化
- 客户端需要知道策略的具体实现细节
- 策略对象需要维护大量状态
提示:策略模式与状态模式经常被混淆。关键区别在于策略模式中的策略是客户主动选择的,而状态模式中的状态转换通常由上下文自动处理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. C++实现策略模式的三种经典方式
2.1 基于继承的经典实现
这是最教科书式的实现方式,完全遵循GoF的设计模式定义:
cpp复制class Strategy {
public:
virtual void execute() = 0;
virtual ~Strategy() = default;
};
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "Executing strategy A\n";
}
};
class Context {
std::unique_ptr<Strategy> strategy;
public:
explicit Context(std::unique_ptr<Strategy> s) : strategy(std::move(s)) {}
void setStrategy(std::unique_ptr<Strategy> s) {
strategy = std::move(s);
}
void executeStrategy() {
strategy->execute();
}
};
这种实现的最大优点是清晰明了,但缺点是需要为每个策略创建新类,可能造成类爆炸。
2.2 基于函数指针的轻量级实现
C++允许更灵活的实现方式,特别是C++11引入lambda后:
cpp复制class Context {
std::function<void()> strategy;
public:
explicit Context(std::function<void()> s) : strategy(s) {}
void setStrategy(std::function<void()> s) {
strategy = s;
}
void executeStrategy() {
strategy();
}
};
// 使用示例
Context ctx([](){
std::cout << "Lambda strategy\n";
});
这种方式减少了类的数量,特别适合简单策略。但失去了接口的明确约束,可能降低代码可读性。
2.3 基于模板的策略模式
模板提供了编译期策略选择的能力:
cpp复制template<typename Strategy>
class Context {
Strategy strategy;
public:
void executeStrategy() {
strategy.execute();
}
};
struct FastStrategy {
void execute() { /* 快速算法实现 */ }
};
struct SafeStrategy {
void execute() { /* 安全算法实现 */ }
};
这种零成本抽象的方案性能最优,但失去了运行时动态切换策略的能力。
3. 实际项目中的策略模式应用技巧
3.1 策略对象的生命周期管理
在长期运行的系统(如服务器)中,策略对象的管理尤为重要。我推荐以下几种方式:
- 共享策略:当策略无状态时,可以共享单例策略对象
cpp复制class SharedStrategy : public Strategy {
// 实现execute()
};
// 使用std::shared_ptr管理
auto strategy = std::make_shared<SharedStrategy>();
Context ctx(strategy);
- 线程局部策略:对于有状态但线程安全的策略
cpp复制thread_local std::unique_ptr<Strategy> tlsStrategy;
- 策略池:频繁创建销毁策略时使用对象池
cpp复制ObjectPool<ConcreteStrategy> pool;
auto strategy = pool.acquire();
3.2 策略组合模式
复杂场景下,可以组合多个策略形成新策略:
cpp复制class CompositeStrategy : public Strategy {
std::vector<std::shared_ptr<Strategy>> strategies;
public:
void addStrategy(std::shared_ptr<Strategy> s) {
strategies.push_back(s);
}
void execute() override {
for(auto& s : strategies) {
s->execute();
}
}
};
这种模式在实现中间件、拦截器链等场景特别有用。
3.3 策略的配置化加载
实际项目中,策略常常需要从配置文件动态加载。我常用的工厂模式变体:
cpp复制class StrategyFactory {
public:
static std::unique_ptr<Strategy> create(const std::string& name) {
if(name == "fast") return std::make_unique<FastStrategy>();
if(name == "safe") return std::make_unique<SafeStrategy>();
throw std::runtime_error("Unknown strategy");
}
};
// 从配置加载
auto strategy = StrategyFactory::create(config.getStrategyName());
更复杂的实现可以结合插件架构,实现完全动态的策略加载。
4. 性能优化与陷阱规避
4.1 虚函数开销与解决方案
传统策略模式通过虚函数实现多态,可能带来性能损耗。优化方案包括:
- CRTP模式:编译期多态
cpp复制template<typename Derived>
class StrategyBase {
public:
void execute() {
static_cast<Derived*>(this)->executeImpl();
}
};
class FastStrategy : public StrategyBase<FastStrategy> {
friend class StrategyBase<FastStrategy>;
void executeImpl() { /* 实现 */ }
};
- 策略内联:对小策略使用函数对象
cpp复制class Context {
std::function<void()> strategy;
public:
template<typename S>
Context(S&& s) : strategy(std::forward<S>(s)) {}
void executeStrategy() {
strategy();
}
};
4.2 线程安全注意事项
策略对象在不同线程间共享时需特别注意:
- 无状态策略是天然线程安全的
- 有状态策略需要同步机制
- 避免在策略执行期间修改策略
我常用的线程安全策略模式实现:
cpp复制class ThreadSafeContext {
std::mutex mtx;
std::shared_ptr<Strategy> strategy;
public:
void setStrategy(std::shared_ptr<Strategy> s) {
std::lock_guard<std::mutex> lock(mtx);
strategy = s;
}
void executeStrategy() {
std::shared_ptr<Strategy> local;
{
std::lock_guard<std::mutex> lock(mtx);
local = strategy;
}
local->execute();
}
};
4.3 内存管理最佳实践
根据策略使用场景选择合适的内存管理方式:
- 策略不常变化:unique_ptr独占所有权
- 策略频繁切换:shared_ptr共享所有权
- 性能敏感场景:自定义内存池
- 嵌入式环境:静态分配策略对象
一个常见错误是在lambda策略中捕获大型对象导致意外拷贝:
cpp复制// 错误示例:大型对象被拷贝
LargeObject obj;
Context ctx([obj]() { /* 使用obj */ }); // obj被拷贝
// 正确做法:按引用捕获并确保生命周期
Context ctx([&obj]() { /* 使用obj */ }); // 需确保obj生命周期足够长
5. 现代C++特性在策略模式中的应用
5.1 使用std::variant实现类型安全策略
C++17引入的variant可以实现类型安全的策略选择:
cpp复制using StrategyVariant = std::variant<FastStrategy, SafeStrategy>;
class VariantContext {
StrategyVariant strategy;
public:
template<typename S>
explicit VariantContext(S&& s) : strategy(std::forward<S>(s)) {}
void executeStrategy() {
std::visit([](auto&& s) {
s.execute();
}, strategy);
}
};
这种方式兼具类型安全和运行时多态的优点。
5.2 概念约束的策略模板
C++20的概念可以更好地约束策略类型:
cpp复制template<typename S>
concept StrategyConcept = requires(S s) {
{ s.execute() } -> std::same_as<void>;
};
template<StrategyConcept S>
class ConceptContext {
S strategy;
public:
void executeStrategy() {
strategy.execute();
}
};
这比传统的接口继承提供了更灵活的约束方式。
5.3 协程与策略模式的结合
对于异步策略,可以结合C++20协程:
cpp复制class AsyncStrategy {
public:
std::future<void> executeAsync() {
co_await std::suspend_always{};
// 异步实现
}
};
class AsyncContext {
std::unique_ptr<AsyncStrategy> strategy;
public:
std::future<void> executeStrategy() {
co_await strategy->executeAsync();
}
};
这种模式在IO密集型应用中特别有用。
6. 测试策略模式的实用技巧
6.1 模拟策略对象
单元测试时,可以使用模拟策略来验证上下文行为:
cpp复制class MockStrategy : public Strategy {
public:
MOCK_METHOD(void, execute, (), (override));
};
TEST(ContextTest, ExecuteStrategy) {
auto mock = std::make_unique<MockStrategy>();
EXPECT_CALL(*mock, execute()).Times(1);
Context ctx(std::move(mock));
ctx.executeStrategy();
}
6.2 基准测试策略性能
使用Google Benchmark比较不同策略性能:
cpp复制template<typename S>
void BM_Strategy(benchmark::State& state) {
S strategy;
for(auto _ : state) {
strategy.execute();
}
}
BENCHMARK_TEMPLATE(BM_Strategy, FastStrategy);
BENCHMARK_TEMPLATE(BM_Strategy, SafeStrategy);
6.3 策略的模糊测试
对于接受输入的策略,可以使用libFuzzer进行模糊测试:
cpp复制extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
InputProcessingStrategy strategy;
try {
strategy.process(data, size);
} catch(...) {}
return 0;
}
7. 从策略模式到策略架构
在大型系统中,策略模式可以扩展为策略架构:
- 策略注册表:全局可访问的策略仓库
cpp复制class StrategyRegistry {
std::unordered_map<std::string, std::function<std::unique_ptr<Strategy>()>> creators;
public:
void registerStrategy(const std::string& name, auto creator) {
creators[name] = creator;
}
std::unique_ptr<Strategy> create(const std::string& name) {
return creators.at(name)();
}
};
- 策略中间件:在策略执行前后插入逻辑
cpp复制class LoggingMiddleware : public Strategy {
std::unique_ptr<Strategy> wrapped;
public:
void execute() override {
log("Before execution");
wrapped->execute();
log("After execution");
}
};
- 策略元编程:根据系统状态自动选择策略
cpp复制class MetaStrategy {
std::unique_ptr<Strategy> selectStrategy() {
if(systemLoad > threshold) return std::make_unique<FastStrategy>();
return std::make_unique<SafeStrategy>();
}
};
在实际的C++项目中,我通常会先从小规模的策略模式开始,随着系统复杂度增加,逐步演进到策略架构。这种渐进式的方式既能保持代码的灵活性,又不会过早引入不必要的复杂性。
