1. 策略模式基础回顾与进阶意义
在C++开发中,策略模式(Strategy Pattern)是我们最常用的设计模式之一。记得刚入行时,我负责一个电商促销系统,第一次用策略模式实现了满减、折扣、赠品等不同促销策略的动态切换。当时觉得这种把算法封装成独立对象的设计简直妙不可言——直到后来系统扩展时,我才真正理解策略模式的进阶用法有多重要。
策略模式的核心在于定义算法族,将每个算法封装起来,使它们可以互相替换。基础实现通常是这样:
cpp复制class Strategy {
public:
virtual void execute() = 0;
};
class ConcreteStrategyA : public Strategy {
public:
void execute() override { /* 算法A实现 */ }
};
class Context {
Strategy* strategy;
public:
void setStrategy(Strategy* s) { strategy = s; }
void executeStrategy() { strategy->execute(); }
};
但实际企业级开发中,我们会遇到更复杂的场景:
- 策略对象需要访问上下文中的大量数据
- 策略组合需要动态生成
- 策略执行需要支持异步和回调
- 策略需要可序列化和版本控制
这些需求推动着我们超越基础的策略模式实现,进入真正的"进阶"领域。接下来,我将分享在实际项目中验证过的五种高阶技巧,它们曾帮我解决过性能瓶颈、降低过系统耦合度,甚至救过紧急上线的项目。
2. 上下文数据的高效传递方案
2.1 传统传参的性能陷阱
新手常犯的错误是简单粗暴地通过构造函数或执行方法传递所有数据:
cpp复制class DiscountStrategy {
public:
virtual double calculate(const Order& order,
const User& user,
const Promotion& promo) = 0;
};
当策略需要访问10+个参数时,这种设计会导致:
- 接口膨胀难以维护
- 每次调用产生大量参数拷贝
- 无法缓存中间计算结果
2.2 上下文对象的最佳实践
我们改进为轻量级上下文对象:
cpp复制class StrategyContext {
private:
const Order& order;
const User& user;
// 其他引用...
mutable std::unordered_map<std::string, double> cache;
public:
// 获取器方法...
void setCache(const std::string& key, double value) const {
cache[key] = value; // const方法修改mutable成员
}
};
class Strategy {
public:
virtual double execute(const StrategyContext& ctx) = 0;
};
关键优化点:
- 使用引用避免拷贝
- mutable缓存支持跨策略计算复用
- 统一访问接口便于监控和日志
实际项目中,这种设计使我们的优惠计算性能提升了3倍,因为多个策略可以共享已计算的商品分类折扣。
3. 策略组合与动态编排
3.1 组合策略模式
当需要应用多个策略时,组合模式(Composite)是自然选择:
cpp复制class CompositeStrategy : public Strategy {
std::vector<std::unique_ptr<Strategy>> strategies;
public:
void addStrategy(std::unique_ptr<Strategy>&& s) {
strategies.push_back(std::move(s));
}
double execute(const StrategyContext& ctx) override {
double result = 0;
for (auto& s : strategies) {
result += s->execute(ctx);
}
return result;
}
};
3.2 基于DSL的动态编排
对于需要运行时配置的场景,我们实现了一个简易DSL解析器:
json复制{
"strategy_chain": [
{
"type": "time_limit",
"params": {"start": "09:00", "end": "12:00"}
},
{
"type": "category_discount",
"params": {"categories": ["electronics"], "rate": 0.9}
}
]
}
对应的策略工厂:
cpp复制class StrategyFactory {
public:
static std::unique_ptr<Strategy> create(const Json& config) {
if (config["type"] == "time_limit") {
return std::make_unique<TimeLimitStrategy>(
config["params"]["start"],
config["params"]["end"]);
}
// 其他策略类型...
}
};
这种设计使我们的营销系统可以实时调整策略组合而不需要重新部署。
4. 异步策略与回调机制
4.1 异步执行的挑战
当策略需要调用外部服务(如风控系统)时,同步调用会导致性能问题。我们采用以下模式:
cpp复制class AsyncStrategy : public Strategy {
public:
using Callback = std::function<void(double)>;
virtual void executeAsync(const StrategyContext& ctx,
Callback cb) = 0;
};
class RiskCheckStrategy : public AsyncStrategy {
public:
void executeAsync(const StrategyContext& ctx,
Callback cb) override {
risk_service.checkAsync(ctx.getUser(),
[cb](RiskResult result) {
cb(result.score);
});
}
};
4.2 超时与熔断机制
生产环境必须添加可靠性保障:
cpp复制class TimeoutStrategyWrapper : public AsyncStrategy {
std::unique_ptr<AsyncStrategy> inner;
std::chrono::milliseconds timeout;
public:
void executeAsync(const StrategyContext& ctx,
Callback cb) override {
auto timer = ctx.getTimer();
auto timed_cb = /* 超时处理逻辑 */;
timer->setTimeout(timeout, [timed_cb] {
timed_cb(/* 默认值或错误 */);
});
inner->executeAsync(ctx, timed_cb);
}
};
5. 策略的元编程优化
5.1 编译时策略选择
对于性能关键路径,我们使用模板元编程:
cpp复制template <typename Discount, typename Tax>
class CheckoutPipeline {
Discount discount;
Tax tax;
public:
double calculate(const Order& o) {
return tax.apply(discount.apply(o.total));
}
};
// 使用示例
using FastPath = CheckoutPipeline<VolumeDiscount, ZeroTax>;
5.2 策略的constexpr优化
C++17允许策略在编译期计算:
cpp复制class ConstexprStrategy {
public:
constexpr double compute(double input) const {
return input * 0.8; // 编译期可计算
}
};
constexpr auto strategy = ConstexprStrategy{};
constexpr double result = strategy.compute(100); // 编译时计算
6. 策略模式的可测试性设计
6.1 依赖注入的进阶用法
我们采用模板策略接口方便mock:
cpp复制template <typename Logger>
class LoggingStrategy : public Strategy {
Logger logger;
public:
double execute(const StrategyContext& ctx) override {
logger.log("Executing strategy");
// ...
}
};
// 测试时
struct MockLogger {
void log(const std::string&) {}
};
using TestStrategy = LoggingStrategy<MockLogger>;
6.2 策略的模糊测试
对策略算法进行属性测试:
cpp复制void test_strategy_properties() {
auto strategy = getStrategy();
for (auto _ : state) {
auto randomCtx = generateRandomContext();
auto result = strategy->execute(randomCtx);
assert(result >= 0 && result <= randomCtx.getMax());
}
}
7. 生产环境中的经验教训
-
策略生命周期管理:曾经因为忘记释放策略对象导致内存泄漏,现在统一使用
std::unique_ptr管理 -
线程安全问题:发现无状态策略比有状态策略性能高30%,因为不需要加锁
-
策略版本兼容:通过给策略添加version()方法,支持多版本策略共存
-
监控埋点:在每个策略执行前后自动记录metrics:
cpp复制class MonitoredStrategy : public Strategy {
std::unique_ptr<Strategy> inner;
Metrics& metrics;
public:
double execute(const StrategyContext& ctx) override {
auto timer = metrics.startTimer();
auto result = inner->execute(ctx);
timer.record();
return result;
}
};
- 最意外的收获:将策略模式应用于单元测试断言逻辑,使我们可以动态切换严格模式/宽松模式