C++实现电商促销规则引擎:面向对象设计、STL应用与性能优化实践
1. 项目概述与核心需求解析
最近在帮一个做电商的朋友优化他们的后台促销系统,他们有个很常见的需求:当用户一次性购买3件或以上商品时,系统需要自动计算并发放一笔额外的“多件购买奖金”。这个需求听起来简单,但实际实现时需要考虑的细节非常多,比如商品如何定义、奖金如何计算、订单状态如何联动等等。朋友最初用一些脚本语言临时处理,但随着订单量增长,性能和稳定性问题就暴露出来了。于是我们决定用C++重新设计这个核心模块,一方面利用C++的高性能处理海量订单,另一方面也通过这个实际项目,把面向对象设计、数据结构、算法这些理论知识真正用起来。
这个“购买3件商品获得奖金”的促销活动,本质上是一个 基于数量的条件触发型奖励规则 。它不关心商品的具体价格或品类(当然实际业务中可能会叠加这些限制),只关注购买数量这个单一维度。用C++来实现,我们不仅要写出能跑通的代码,更要设计出 易维护、可扩展、高性能 的解决方案。毕竟促销活动千变万化,今天可能是“买3件”,明天可能就是“买不同类目的3件”或者“买满300元再送一件”。一个好的程序设计,应该能从容应对这些变化。
从技术角度看,这个项目会涉及几个核心的C++知识点:首先是 类的设计 ,我们需要抽象出“商品”、“订单”、“促销规则”这些业务实体;其次是 标准模板库(STL) 的应用,比如用 vector 管理商品列表,用 map 或 unordered_map 来高效查询和匹配规则;最后是 算法的实现 ,即如何高效地判断一个订单是否满足“3件”的条件,并计算出应得的奖金。这比单纯写一个 if (count >= 3) { bonus = ... } 要复杂得多,我们需要考虑并发处理、规则优先级、异常情况(比如退货)等一系列现实问题。
2. 系统整体架构与类设计思路
2.1 核心类的识别与职责划分
接到需求后,我的第一反应不是马上开始写代码,而是先画图,理清系统中到底有哪些“东西”以及它们之间的关系。这是面向对象设计的精髓。在这个促销系统中,我识别出以下几个核心类:
- 商品类(Product) :这是系统最基本的单元。每个商品至少需要一个唯一标识(比如ID或SKU)、一个名称和一个价格。在实际电商系统中,商品属性可能非常复杂(库存、类别、上下架状态等),但为了聚焦核心逻辑,我们先从简。
- 订单项类(OrderItem) :代表订单中的一个具体商品及其购买数量。它应该包含一个
Product对象的引用(或智能指针)以及一个购买数量quantity。这里为什么不用Product对象而用引用?主要是为了避免数据冗余。如果订单里有10件同样的商品,我们不需要在内存里存10个一模一样的Product对象,只需存10个指向同一个Product的引用即可。 - 订单类(Order) :这是本次项目的核心。一个订单包含多个
OrderItem,以及订单状态、总金额、应得奖金等属性。它的一个关键方法是计算当前订单是否满足某个促销规则,并应用该规则。 - 促销规则基类(PromotionRule) :这是实现灵活性的关键。我们将促销规则抽象成一个基类,其中定义一个纯虚函数
bool isEligible(const Order& order)用于判断订单是否满足条件,以及另一个纯虚函数double calculateBonus(const Order& order)用于计算奖金。这样,任何具体的促销规则(如“满3件”、“满减”、“折扣”)都从它派生。 - 具体促销规则类(ThreeItemBonusRule) :继承自
PromotionRule,专门实现“购买3件商品获得奖金”的逻辑。这里就是业务逻辑的核心所在。 - 促销引擎类(PromotionEngine) :负责管理所有的促销规则,并为一个给定的订单匹配和应用最合适的规则(有时可能有多个规则叠加或互斥)。它起到了协调和调度作用。
设计心得 :在初期设计时,一定要把“变”和“不变”分开。 “不变”的是促销引擎处理订单、应用规则的流程 。 “变”的是具体的规则内容 。通过抽象出
PromotionRule基类,我们把易变的部分封装起来,以后新增“第二件半价”或“会员双倍积分”规则时,只需增加新的派生类,而无需修改引擎的核心逻辑。这就是著名的“开闭原则”(对扩展开放,对修改关闭)。
2.2 类之间的关系与UML草图
虽然不画正式UML图,但在脑子里或草稿纸上厘清关系至关重要:
Order聚合 了多个OrderItem(“拥有”的关系,订单不存在,订单项也无意义)。OrderItem关联 到一个Product(“知道”的关系,订单项需要知道是哪个商品)。PromotionEngine组合 了多个PromotionRule(引擎“由”规则集合构成,生命周期通常一致)。ThreeItemBonusRule继承 自PromotionRule(“是一个”的关系,三件奖励规则是一种促销规则)。
这种设计使得系统高度模块化。例如,商品管理的改动被限制在 Product 类内;计算逻辑的改动被限制在具体的 PromotionRule 派生类中;而订单处理的流程则封装在 Order 和 PromotionEngine 里。各司其职,耦合度低。
3. 核心类的详细实现与代码解析
3.1 基础数据类的实现
我们从最简单的 Product 类开始。为了专注于逻辑,我们暂时忽略复杂的构造、拷贝控制(Rule of Three/Five),先提供最基本的功能。
// product.h
#ifndef PRODUCT_H
#define PRODUCT_H
#include <string>
class Product {
public:
Product(const std::string& id, const std::string& name, double price)
: id_(id), name_(name), price_(price) {
if (price < 0) {
throw std::invalid_argument("Product price cannot be negative.");
}
}
// 简单的getter方法,使用const引用返回避免拷贝
const std::string& getId() const { return id_; }
const std::string& getName() const { return name_; }
double getPrice() const { return price_; }
// 实际项目中可能还需要setter,但促销计算中通常只读
private:
std::string id_; // 商品唯一标识,如“SKU12345”
std::string name_; // 商品名称
double price_; // 商品单价,用double需注意精度问题,实际金融计算可能用整数分表示
};
#endif // PRODUCT_H
注意 :这里直接使用
double存储价格是为了示例简单。在真实的电商或金融系统中,由于浮点数的精度问题,货币金额通常用 整数 表示(例如以“分”为单位),或者使用定点数库。这是一个非常重要的实战细节。
接下来是 OrderItem ,它关联到一个 Product 并记录数量。
// order_item.h
#ifndef ORDER_ITEM_H
#define ORDER_ITEM_H
#include "product.h"
#include <memory> // 用于std::shared_ptr
class OrderItem {
public:
// 使用智能指针管理Product,方便内存管理,也表达“共享”语义
OrderItem(std::shared_ptr<Product> product, int quantity)
: product_(std::move(product)), quantity_(quantity) {
if (!product_) {
throw std::invalid_argument("OrderItem must have a valid product.");
}
if (quantity <= 0) {
throw std::invalid_argument("OrderItem quantity must be positive.");
}
}
std::shared_ptr<Product> getProduct() const { return product_; }
int getQuantity() const { return quantity_; }
// 计算本订单项的总价
double getTotalPrice() const {
return product_->getPrice() * quantity_;
}
private:
std::shared_ptr<Product> product_; // 指向商品的智能指针
int quantity_; // 购买数量
};
#endif // ORDER_ITEM_H
使用 std::shared_ptr 的好处是自动内存管理。当多个订单项包含同一商品时,它们共享同一个 Product 对象的内存,当最后一个引用它的 OrderItem 被销毁时, Product 对象也会自动释放。这比原始指针安全得多。
3.2 促销规则基类与具体实现
这是本次项目的灵魂所在。我们先定义抽象基类:
// promotion_rule.h
#ifndef PROMOTION_RULE_H
#define PROMOTION_RULE_H
class Order; // 前向声明,避免头文件循环包含
class PromotionRule {
public:
virtual ~PromotionRule() = default; // 虚析构函数,确保派生类正确释放
// 纯虚函数,判断订单是否有资格享受此促销
virtual bool isEligible(const Order& order) const = 0;
// 纯虚函数,计算此促销规则下订单应得的奖金(或折扣金额)
virtual double calculateBonus(const Order& order) const = 0;
// 可选:获取规则描述,用于日志或展示
virtual std::string getDescription() const = 0;
};
#endif // PROMOTION_RULE_H
现在,来实现具体的“买3件得奖金”规则:
// three_item_bonus_rule.h
#ifndef THREE_ITEM_BONUS_RULE_H
#define THREE_ITEM_BONUS_RULE_H
#include "promotion_rule.h"
#include "order.h"
#include <string>
class ThreeItemBonusRule : public PromotionRule {
public:
// 构造函数,可以配置奖金金额和是否要求不同商品
ThreeItemBonusRule(double bonusAmount, bool requireDistinctItems = false)
: bonusAmount_(bonusAmount), requireDistinctItems_(requireDistinctItems) {
if (bonusAmount <= 0) {
throw std::invalid_argument("Bonus amount must be positive.");
}
}
bool isEligible(const Order& order) const override {
int itemCount = 0;
if (requireDistinctItems_) {
// 计算不同商品的数量(基于Product ID)
std::unordered_set<std::string> distinctProductIds;
const auto& items = order.getItems();
for (const auto& item : items) {
distinctProductIds.insert(item.getProduct()->getId());
}
itemCount = distinctProductIds.size();
} else {
// 计算商品总件数(含重复)
itemCount = order.getTotalQuantity();
}
return itemCount >= 3;
}
double calculateBonus(const Order& order) const override {
if (isEligible(order)) {
return bonusAmount_;
}
return 0.0; // 不满足条件则无奖金
}
std::string getDescription() const override {
std::string desc = "Buy 3 items get bonus: $" + std::to_string(bonusAmount_);
if (requireDistinctItems_) {
desc += " (requires 3 distinct items)";
}
return desc;
}
private:
double bonusAmount_; // 奖金金额
bool requireDistinctItems_; // 是否要求3件为不同商品
};
#endif // THREE_ITEM_BONUS_RULE_H
这个实现有几个关键点:
- 灵活性 :通过
requireDistinctItems_参数,一条规则可以同时支持“任意3件”和“3件不同商品”两种业务场景。这体现了代码的可配置性。 - 性能 :在
isEligible中,根据条件选择不同的计算策略。如果只算总件数,直接调用order.getTotalQuantity(),其内部可能已缓存结果,是O(1)操作。如果要求不同商品,则需要遍历所有订单项并借助unordered_set去重,是O(n)操作。在规则设计时就要考虑性能影响。 - 健壮性 :在构造函数中进行参数校验,防止创建一条“负奖金”的无效规则。
3.3 订单类的核心实现
Order 类需要聚合 OrderItem ,并提供必要的查询和计算方法。这里我们使用 std::vector 来存储订单项。
// order.h
#ifndef ORDER_H
#define ORDER_H
#include "order_item.h"
#include <vector>
#include <memory>
#include <string>
class Order {
public:
Order(const std::string& orderId) : orderId_(orderId), totalBonus_(0.0) {}
void addItem(std::shared_ptr<Product> product, int quantity) {
// 检查是否已存在相同商品,存在则增加数量,否则新增(简化逻辑,实际可能更复杂)
// 这里为了清晰,我们每次直接新增一个OrderItem
items_.emplace_back(std::make_shared<OrderItem>(product, quantity));
// 清空缓存,因为订单内容变了
cachedTotalQuantity_.reset();
cachedTotalPrice_.reset();
}
const std::vector<OrderItem>& getItems() const { return items_; }
std::string getOrderId() const { return orderId_; }
double getTotalBonus() const { return totalBonus_; }
void setTotalBonus(double bonus) { totalBonus_ = bonus; }
// 计算订单中商品总件数(含重复)
int getTotalQuantity() const {
if (!cachedTotalQuantity_.has_value()) {
int total = 0;
for (const auto& item : items_) {
total += item.getQuantity();
}
cachedTotalQuantity_ = total;
}
return cachedTotalQuantity_.value();
}
// 计算订单原始总金额(不含奖金)
double calculateOriginalTotal() const {
if (!cachedTotalPrice_.has_value()) {
double total = 0.0;
for (const auto& item : items_) {
total += item.getTotalPrice();
}
cachedTotalPrice_ = total;
}
return cachedTotalPrice_.value();
}
// 计算最终应付金额(原始金额 - 奖金)
double calculateFinalTotal() const {
return calculateOriginalTotal() - totalBonus_;
}
private:
std::string orderId_;
std::vector<OrderItem> items_;
double totalBonus_; // 该订单获得的总奖金
// 使用std::optional进行简单缓存,避免重复计算
mutable std::optional<int> cachedTotalQuantity_;
mutable std::optional<double> cachedTotalPrice_;
};
#endif // ORDER_H
实现技巧 :注意
getTotalQuantity和calculateOriginalTotal方法中的 缓存(Cache) 优化。因为订单一旦创建,其商品项在计算促销时是只读的,但这两个值可能被频繁调用(例如每个促销规则都可能需要判断总件数或总价)。通过mutable的std::optional成员变量缓存计算结果,第一次计算后,后续调用直接返回缓存值,避免了O(n)的重复遍历。这是一种非常实用的性能优化手段,在业务逻辑复杂、计算成本高时效果显著。
3.4 促销引擎的实现
PromotionEngine 是大脑,它持有规则集合并为订单应用规则。
// promotion_engine.h
#ifndef PROMOTION_ENGINE_H
#define PROMOTION_ENGINE_H
#include "promotion_rule.h"
#include "order.h"
#include <vector>
#include <memory>
class PromotionEngine {
public:
// 添加一条促销规则
void addRule(std::unique_ptr<PromotionRule> rule) {
rules_.push_back(std::move(rule));
}
// 为订单应用所有促销规则,并计算总奖金
void applyRules(Order& order) {
double totalBonus = 0.0;
std::vector<std::string> appliedRuleDescriptions;
for (const auto& rule : rules_) {
if (rule->isEligible(order)) {
double bonus = rule->calculateBonus(order);
if (bonus > 0) {
totalBonus += bonus;
appliedRuleDescriptions.push_back(rule->getDescription());
// 这里可以添加日志:规则X被应用,奖金Y元
}
}
}
order.setTotalBonus(totalBonus);
// 可以在这里记录所有被应用的规则描述,方便对账
// logAppliedRules(order.getOrderId(), appliedRuleDescriptions);
}
// 获取当前引擎中的所有规则描述(用于监控或调试)
std::vector<std::string> getAllRuleDescriptions() const {
std::vector<std::string> descs;
for (const auto& rule : rules_) {
descs.push_back(rule->getDescription());
}
return descs;
}
private:
std::vector<std::unique_ptr<PromotionRule>> rules_;
};
#endif // PROMOTION_ENGINE_H
引擎的设计采用了 策略模式(Strategy Pattern) 的变体。它将具体的促销算法(即各个 PromotionRule )封装成独立的对象,使得它们可以独立于引擎进行变化和扩展。 applyRules 方法遍历所有规则,依次判断并累加奖金。这里有一个重要的业务决策点: 规则是累加的还是互斥的? 上述实现是累加模式(满足多条规则,奖金叠加)。如果业务要求只能应用一条最优规则,则需要修改遍历逻辑,找到 calculateBonus 返回值最大的那条规则,或者为规则设置优先级字段。
4. 主程序流程与集成测试
有了所有这些类,我们就可以编写一个主程序来模拟整个流程了。这相当于我们程序的“集成测试”或“演示场景”。
// main.cpp
#include <iostream>
#include <memory>
#include "product.h"
#include "order.h"
#include "three_item_bonus_rule.h"
#include "promotion_engine.h"
int main() {
try {
// 1. 创建一些商品
auto product1 = std::make_shared<Product>("P001", "T-Shirt", 29.99);
auto product2 = std::make_shared<Product>("P002", "Coffee Mug", 12.50);
auto product3 = std::make_shared<Product>("P003", "Notebook", 5.99);
auto product4 = std::make_shared<Product>("P004", "Pen", 1.99);
// 2. 创建促销规则:买任意3件,奖励5元
auto rule1 = std::make_unique<ThreeItemBonusRule>(5.0, false);
// 再创建一条规则:买3件不同商品,奖励8元(规则可叠加)
auto rule2 = std::make_unique<ThreeItemBonusRule>(8.0, true);
// 3. 配置促销引擎
PromotionEngine engine;
engine.addRule(std::move(rule1));
engine.addRule(std::move(rule2));
std::cout << "Loaded promotion rules:\n";
for (const auto& desc : engine.getAllRuleDescriptions()) {
std::cout << " - " << desc << "\n";
}
std::cout << "-------------------------\n";
// 4. 模拟多个订单场景
// 场景A:订单只有2件商品(不满足条件)
{
Order orderA("ORDER-001");
orderA.addItem(product1, 1); // 1件T恤
orderA.addItem(product2, 1); // 1个杯子
engine.applyRules(orderA);
std::cout << "Order " << orderA.getOrderId() << ":\n";
std::cout << " Items: 2, Original Total: $" << orderA.calculateOriginalTotal()
<< ", Bonus: $" << orderA.getTotalBonus()
<< ", Final Total: $" << orderA.calculateFinalTotal() << "\n\n";
}
// 场景B:订单有3件相同商品(满足规则1,不满足规则2)
{
Order orderB("ORDER-002");
orderB.addItem(product1, 3); // 3件相同的T恤
engine.applyRules(orderB);
std::cout << "Order " << orderB.getOrderId() << ":\n";
std::cout << " Items: 3 (same), Original Total: $" << orderB.calculateOriginalTotal()
<< ", Bonus: $" << orderB.getTotalBonus()
<< ", Final Total: $" << orderB.calculateFinalTotal() << "\n\n";
}
// 场景C:订单有4件,且来自3种不同商品(同时满足两条规则)
{
Order orderC("ORDER-003");
orderC.addItem(product1, 1); // T恤
orderC.addItem(product2, 2); // 2个杯子
orderC.addItem(product3, 1); // 1个笔记本
engine.applyRules(orderC);
std::cout << "Order " << orderC.getOrderId() << ":\n";
std::cout << " Items: 4 (3 distinct), Original Total: $" << orderC.calculateOriginalTotal()
<< ", Bonus: $" << orderC.getTotalBonus()
<< ", Final Total: $" << orderC.calculateFinalTotal() << "\n\n";
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
编译并运行这个程序(假设使用g++):
g++ -std=c++17 -o promotion_system main.cpp
./promotion_system
预期的输出应该是:
Loaded promotion rules:
- Buy 3 items get bonus: $5.000000
- Buy 3 items get bonus: $8.000000 (requires 3 distinct items)
-------------------------
Order ORDER-001:
Items: 2, Original Total: $42.49, Bonus: $0, Final Total: $42.49
Order ORDER-002:
Items: 3 (same), Original Total: $89.97, Bonus: $5, Final Total: $84.97
Order ORDER-003:
Items: 4 (3 distinct), Original Total: $61.47, Bonus: $13, Final Total: $48.47
从输出可以清晰看到:
- 订单001只有2件商品,不满足任何规则,奖金为0。
- 订单002有3件相同商品,只触发了第一条规则(任意3件),获得5元奖金。
- 订单003有4件商品,且来自3种不同商品,同时触发了两条规则(5+8),获得了13元奖金。
这个简单的测试验证了我们核心逻辑的正确性。
5. 性能优化与高级特性探讨
基础功能实现后,我们需要思考如何让它更健壮、更高效。这部分往往是区分普通代码和高质量代码的关键。
5.1 利用STL算法提升代码简洁性
在 ThreeItemBonusRule::isEligible 中,我们手动写了循环来计算不同商品数量。其实,使用STL算法可以让意图更清晰:
bool isEligible(const Order& order) const override {
const auto& items = order.getItems();
if (requireDistinctItems_) {
// 使用std::unordered_set和范围for循环
std::unordered_set<std::string> distinctIds;
for (const auto& item : items) {
distinctIds.insert(item.getProduct()->getId());
}
return distinctIds.size() >= 3;
} else {
// 使用std::accumulate计算总数量
int totalQty = std::accumulate(items.begin(), items.end(), 0,
[](int sum, const OrderItem& item) { return sum + item.getQuantity(); });
return totalQty >= 3;
}
}
std::accumulate 是 <numeric> 头文件中的算法,它清晰地表达了“累加”这个操作。对于更复杂的条件判断,还可以使用 std::all_of , std::any_of , std::count_if 等算法,让代码更具表达力。
5.2 规则优先级与冲突解决
在实际电商系统中,促销规则往往不是简单累加。常见的冲突解决策略有:
- 最优规则 :只应用奖金最高的一条规则。
- 优先级 :每条规则有优先级,高优先级规则先应用,且可能阻止低优先级规则。
- 互斥组 :某些规则属于同一组,组内只能应用一条。
为了实现这些,我们需要修改 PromotionRule 基类和 PromotionEngine::applyRules 的逻辑。例如,为规则增加优先级字段:
class PromotionRule {
public:
// ... 其他成员 ...
virtual int getPriority() const { return 0; } // 默认优先级
};
// 在PromotionEngine中,先按优先级排序规则
void applyRules(Order& order) {
// 临时存储可应用的规则及其奖金
std::vector<std::pair<PromotionRule*, double>> applicableRules;
for (const auto& rule : rules_) {
if (rule->isEligible(order)) {
double bonus = rule->calculateBonus(order);
if (bonus > 0) {
applicableRules.emplace_back(rule.get(), bonus);
}
}
}
// 按优先级降序排序(优先级数字大的优先)
std::sort(applicableRules.begin(), applicableRules.end(),
[](const auto& a, const auto& b) {
return a.first->getPriority() > b.first->getPriority();
});
// 应用逻辑:这里假设只应用最高优先级的一条
if (!applicableRules.empty()) {
order.setTotalBonus(applicableRules.front().second);
}
}
5.3 使用多态与工厂模式管理规则
如果促销规则类型非常多(满减、折扣、赠品、包邮等),在代码中到处 new 具体的规则对象会很混乱。我们可以引入 工厂模式(Factory Pattern) 来集中创建规则对象。
class PromotionRuleFactory {
public:
static std::unique_ptr<PromotionRule> createRule(const std::string& ruleType, const std::string& config) {
if (ruleType == "THREE_ITEM_BONUS") {
// 解析config字符串,例如"5.0,false"
size_t commaPos = config.find(',');
double amount = std::stod(config.substr(0, commaPos));
bool distinct = (config.substr(commaPos + 1) == "true");
return std::make_unique<ThreeItemBonusRule>(amount, distinct);
}
// else if (ruleType == "DISCOUNT") { ... }
// else if (ruleType == "FREE_SHIPPING") { ... }
else {
throw std::runtime_error("Unknown rule type: " + ruleType);
}
}
};
这样,规则配置可以从文件或数据库读取,然后通过工厂动态创建,实现了 配置化 ,无需修改代码就能上线新规则。
5.4 并发安全考虑
如果促销引擎在Web服务器等多线程环境中使用,就必须考虑并发安全。 PromotionEngine 的 applyRules 方法在遍历 rules_ 向量时,如果另一个线程正在添加或删除规则,会导致未定义行为。简单的解决方案是使用互斥锁( std::mutex ):
#include <mutex>
class PromotionEngine {
public:
void addRule(std::unique_ptr<PromotionRule> rule) {
std::lock_guard<std::mutex> lock(rulesMutex_);
rules_.push_back(std::move(rule));
}
void applyRules(Order& order) {
std::vector<std::unique_ptr<PromotionRule>> localRulesCopy;
{
std::lock_guard<std::mutex> lock(rulesMutex_);
// 复制规则指针(浅拷贝,规则对象本身不变)
localRulesCopy.reserve(rules_.size());
for (const auto& rule : rules_) {
// 注意:这里复制的是unique_ptr,但为了线程安全,我们只读不写,
// 所以可以复制指向同一对象的shared_ptr,或者确保规则对象本身是线程安全的。
// 更安全的做法是复制规则对象的副本,但成本高。这里假设规则对象是只读的。
// 实际中,可能需要更精细的锁策略或使用读写锁(std::shared_mutex)。
}
} // 锁的作用域结束,释放锁
// 使用localRulesCopy进行计算,避免长时间持有锁
// ... 计算逻辑 ...
}
private:
std::vector<std::unique_ptr<PromotionRule>> rules_;
mutable std::mutex rulesMutex_;
};
重要提示 :多线程编程非常复杂。上面的示例只是一个粗略的示意。在真实的高并发场景中,需要仔细设计锁的粒度,避免死锁,并考虑使用无锁数据结构或
std::shared_mutex(读写锁)来优化读多写少的场景。如果规则在运行时不会改变,那么最简单的方式是在系统初始化时加载所有规则,之后只读,这样就无需加锁。
6. 常见问题、调试技巧与扩展方向
6.1 调试与日志
在开发过程中,遇到规则不生效或奖金计算错误是常事。除了用调试器(如GDB或VS Debugger)逐行跟踪,添加详细的日志是线上排查问题的关键。可以在 PromotionEngine::applyRules 和各个规则的 isEligible 、 calculateBonus 中加入日志输出。
void applyRules(Order& order) {
double totalBonus = 0.0;
for (const auto& rule : rules_) {
bool eligible = rule->isEligible(order);
std::cout << "[DEBUG] Rule \"" << rule->getDescription()
<< "\" for order " << order.getOrderId()
<< " eligible: " << std::boolalpha << eligible << std::endl;
if (eligible) {
double bonus = rule->calculateBonus(order);
std::cout << "[DEBUG] Bonus calculated: $" << bonus << std::endl;
totalBonus += bonus;
}
}
order.setTotalBonus(totalBonus);
}
在实际项目中,应使用更专业的日志库(如spdlog、glog),并控制日志级别(DEBUG, INFO, WARN, ERROR),在开发环境开启DEBUG,生产环境关闭。
6.2 处理浮点数精度
如前所述,金融计算中直接使用 double 比较或累加可能导致精度误差。一个常见的解决方案是 以最小货币单位(如分)存储金额为整数 。
class Money {
public:
Money(long cents) : cents_(cents) {}
static Money fromDouble(double dollars) {
// 四舍五入到分
return Money(static_cast<long>(std::round(dollars * 100.0)));
}
double toDouble() const { return cents_ / 100.0; }
long getCents() const { return cents_; }
// 重载运算符:+, -, *, /, ==, < 等
private:
long cents_; // 以分为单位
};
然后在 Product 、 Order 、 PromotionRule 中全部使用 Money 类型代替 double 。这样,所有的加减比较都是精确的整数运算。
6.3 规则引擎的扩展
当前的设计已经为扩展打下了良好基础。未来可以轻松添加以下功能:
- 组合规则 :创建一个
CompositePromotionRule,它包含多个子规则,只有所有子规则都满足时才生效(AND逻辑),或者任意一个满足就生效(OR逻辑)。 - 条件规则 :规则生效需要额外条件,如仅限特定用户等级、特定时间段、特定商品品类等。可以在
PromotionRule基类中增加一个bool checkCondition(const Order&, const User&, ...)的虚函数,或者将这些条件也设计成独立的策略对象。 - 规则持久化 :将规则配置存储在数据库(如MySQL、Redis)中,系统启动时加载,并支持热更新(通过监听配置变更或提供管理接口)。
6.4 单元测试的重要性
对于这样一个核心业务模块,编写单元测试是保证代码质量、防止回归错误的最佳实践。可以使用Google Test、Catch2等测试框架。
// 使用Google Test的示例
#include <gtest/gtest.h>
TEST(ThreeItemBonusRuleTest, EligibleForThreeItems) {
auto rule = ThreeItemBonusRule(5.0, false);
Order order("TEST-001");
auto p = std::make_shared<Product>("P1", "Test", 10.0);
order.addItem(p, 3); // 买3件
EXPECT_TRUE(rule.isEligible(order));
EXPECT_DOUBLE_EQ(5.0, rule.calculateBonus(order));
}
TEST(ThreeItemBonusRuleTest, NotEligibleForTwoItems) {
auto rule = ThreeItemBonusRule(5.0, false);
Order order("TEST-002");
auto p = std::make_shared<Product>("P1", "Test", 10.0);
order.addItem(p, 2); // 只买2件
EXPECT_FALSE(rule.isEligible(order));
EXPECT_DOUBLE_EQ(0.0, rule.calculateBonus(order));
}
通过为每个类、每个关键方法编写测试用例,可以确保每次修改都不会破坏原有功能,这也是专业软件开发的标准流程。
更多推荐




所有评论(0)