1. 项目概述与核心需求解析

最近在帮一个做电商的朋友优化他们的后台促销系统,他们有个很常见的需求:当用户一次性购买3件或以上商品时,系统需要自动计算并发放一笔额外的“多件购买奖金”。这个需求听起来简单,但实际实现时需要考虑的细节非常多,比如商品如何定义、奖金如何计算、订单状态如何联动等等。朋友最初用一些脚本语言临时处理,但随着订单量增长,性能和稳定性问题就暴露出来了。于是我们决定用C++重新设计这个核心模块,一方面利用C++的高性能处理海量订单,另一方面也通过这个实际项目,把面向对象设计、数据结构、算法这些理论知识真正用起来。

这个“购买3件商品获得奖金”的促销活动,本质上是一个 基于数量的条件触发型奖励规则 。它不关心商品的具体价格或品类(当然实际业务中可能会叠加这些限制),只关注购买数量这个单一维度。用C++来实现,我们不仅要写出能跑通的代码,更要设计出 易维护、可扩展、高性能 的解决方案。毕竟促销活动千变万化,今天可能是“买3件”,明天可能就是“买不同类目的3件”或者“买满300元再送一件”。一个好的程序设计,应该能从容应对这些变化。

从技术角度看,这个项目会涉及几个核心的C++知识点:首先是 类的设计 ,我们需要抽象出“商品”、“订单”、“促销规则”这些业务实体;其次是 标准模板库(STL) 的应用,比如用 vector 管理商品列表,用 map unordered_map 来高效查询和匹配规则;最后是 算法的实现 ,即如何高效地判断一个订单是否满足“3件”的条件,并计算出应得的奖金。这比单纯写一个 if (count >= 3) { bonus = ... } 要复杂得多,我们需要考虑并发处理、规则优先级、异常情况(比如退货)等一系列现实问题。

2. 系统整体架构与类设计思路

2.1 核心类的识别与职责划分

接到需求后,我的第一反应不是马上开始写代码,而是先画图,理清系统中到底有哪些“东西”以及它们之间的关系。这是面向对象设计的精髓。在这个促销系统中,我识别出以下几个核心类:

  1. 商品类(Product) :这是系统最基本的单元。每个商品至少需要一个唯一标识(比如ID或SKU)、一个名称和一个价格。在实际电商系统中,商品属性可能非常复杂(库存、类别、上下架状态等),但为了聚焦核心逻辑,我们先从简。
  2. 订单项类(OrderItem) :代表订单中的一个具体商品及其购买数量。它应该包含一个 Product 对象的引用(或智能指针)以及一个购买数量 quantity 。这里为什么不用 Product 对象而用引用?主要是为了避免数据冗余。如果订单里有10件同样的商品,我们不需要在内存里存10个一模一样的 Product 对象,只需存10个指向同一个 Product 的引用即可。
  3. 订单类(Order) :这是本次项目的核心。一个订单包含多个 OrderItem ,以及订单状态、总金额、应得奖金等属性。它的一个关键方法是计算当前订单是否满足某个促销规则,并应用该规则。
  4. 促销规则基类(PromotionRule) :这是实现灵活性的关键。我们将促销规则抽象成一个基类,其中定义一个纯虚函数 bool isEligible(const Order& order) 用于判断订单是否满足条件,以及另一个纯虚函数 double calculateBonus(const Order& order) 用于计算奖金。这样,任何具体的促销规则(如“满3件”、“满减”、“折扣”)都从它派生。
  5. 具体促销规则类(ThreeItemBonusRule) :继承自 PromotionRule ,专门实现“购买3件商品获得奖金”的逻辑。这里就是业务逻辑的核心所在。
  6. 促销引擎类(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

这个实现有几个关键点:

  1. 灵活性 :通过 requireDistinctItems_ 参数,一条规则可以同时支持“任意3件”和“3件不同商品”两种业务场景。这体现了代码的可配置性。
  2. 性能 :在 isEligible 中,根据条件选择不同的计算策略。如果只算总件数,直接调用 order.getTotalQuantity() ,其内部可能已缓存结果,是O(1)操作。如果要求不同商品,则需要遍历所有订单项并借助 unordered_set 去重,是O(n)操作。在规则设计时就要考虑性能影响。
  3. 健壮性 :在构造函数中进行参数校验,防止创建一条“负奖金”的无效规则。

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

从输出可以清晰看到:

  1. 订单001只有2件商品,不满足任何规则,奖金为0。
  2. 订单002有3件相同商品,只触发了第一条规则(任意3件),获得5元奖金。
  3. 订单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 规则优先级与冲突解决

在实际电商系统中,促销规则往往不是简单累加。常见的冲突解决策略有:

  1. 最优规则 :只应用奖金最高的一条规则。
  2. 优先级 :每条规则有优先级,高优先级规则先应用,且可能阻止低优先级规则。
  3. 互斥组 :某些规则属于同一组,组内只能应用一条。

为了实现这些,我们需要修改 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));
}

通过为每个类、每个关键方法编写测试用例,可以确保每次修改都不会破坏原有功能,这也是专业软件开发的标准流程。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐