ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零设计自动售货机(Vending Machine):需求、类设计与 C++ 实现深度解析

从零设计自动售货机(Vending Machine):需求、类设计与 C++ 实现深度解析 示例工程【免费下载链接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.项目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design点击查看免费下载本文以 awesome-low-level-design 仓库中的 自动售货机 C 解决方案 为主线系统拆解低层设计LLD面试中经典的 Vending Machine 问题从需求澄清、核心类与枚举划分到 C 源码级别的库存管理、交易流程、异常处理与演示程序。读完本文你将掌握如何用领域建模 状态机思想State Pattern 单例约束Singleton Pattern组织一个多商品、多面额、支持并发与补货的售货机系统并能够在面试中清晰复述其设计取舍。一、需求分析把模糊问题转化为可验证的功能清单原文档开篇即给出 7 条需求这是整个设计的验收标准。在做 LLD 面试题时第一步永远是澄清需求边界而非直接写代码。这 7 条可以归纳为四类核心关注点商品管理支持多种商品且不同商品具有不同价格与库存数量。支付与找零接受不同面额的硬币Coin与纸币Note商品售出后如付款有盈余需返回找零。交易与一致性记录可用商品与数量变化支持并发交易并保证数据一致性。运维接口与异常场景提供补货restock与收款collect money接口对资金不足insufficient funds、商品缺货out-of-stock等异常场景有明确处理。对照仓库中的 C 实现 VendingMachine.cpp这些需求被逐一落地为方法签名商品管理 →addProduct(name, price, quantity)、removeProduct(productId)、restockProduct(productId, quantity)、updatePrice(productId, price)支付与找零 →purchaseProduct(productId, quantity, payment)付款额与总价比较后多退少补由调用方结算运维 →addCash(amount)、withdrawCash(amount)、setOperational(status)异常场景 →purchaseProduct对不可用商品库存不足付款不足均返回nullptr由调用方感知失败。二、核心类、接口与枚举的设计骨架原文档第 12 行起定义了设计骨架共 8 个核心构件。理解这套命名对后续阅读任何一种语言实现都至关重要构件职责Product表示一件商品属性含名称、价格、数量等Coin / Note 枚举描述售货机接受的不同硬币与纸币面额Inventory管理商品与库存数量使用并发哈希表concurrent hash map保证线程安全VendingMachineState 接口定义 idle、ready、dispense 等状态下机器的行为IdleState / ReadyState / DispenseState状态接口的具体实现封装各状态下的行为差异VendingMachine主类遵循 Singleton 模式保证全系统唯一实例VendingMachineDemo演示程序加商品、选商品、投币、出货、找零全流程需要特别说明的是原文档描述的是理想化设计State Pattern Singleton 并发 Inventory而本仓库的 C 版实现是一套教学简化版——它以Product、Transaction、VendingMachine三个类直接落地未引入枚举与状态类。但仓库中 Go 版实现含coin.go、note.go、inventory.go、state.go、C# 版实现含Enum/Coin.cs、States/IdleState.cs、States/HasMoneyState.cs、States/DispensingState.cs、以及 Java 版 vendingmachine 和 TypeScript 版 VendingMachine 则完整实现了文档描述的状态机与枚举设计。因此下文以 C 实现为主干做逐类分析并交叉对照文档骨架与多语言实现帮助读者理解文档设计与具体落地的差异。三、Product 类商品模型的领域边界Product.hpp 将商品建模为五个私有字段与一组操作class Product { private: std::string productId; std::string name; double price; int quantity; bool available; public: Product(std::string productId, std::string name, double price, int quantity 0); std::string getProductId() const; std::string getName() const; double getPrice() const; int getQuantity() const; bool isAvailable() const; void setPrice(double price); void setQuantity(int quantity); void setAvailable(bool status); void addQuantity(int amount); bool removeQuantity(int amount); void displayInfo() const; };关键设计点isAvailable()是派生状态而非独立事实。在 Product.cpp 中它的实现是available quantity 0即上架标志 库存大于零同时成立才算可售。这避免了available布尔值与quantity相互矛盾的数据不一致问题。removeQuantity(int amount)带前置校验if (amount quantity)才扣减并返回true否则返回false不动库存。这是对out-of-stock 异常场景的第一道防线。displayInfo()输出标准化使用iomanip的std::fixed std::setprecision(2)将价格格式化为两位小数避免浮点输出的杂乱属于工程细节上的加分项。在文档设计的完整版中Product 还应与Coin/Note枚举配合机器只接受枚举中列出的面额这为找零可行性提供了建模基础——真实售货机还需检查硬币箱中是否有足够零钱这是 C 简化版未涉及、但面试中可主动提出的扩展点。四、Transaction 类让交易可审计C 实现额外引入了一个原文档未单列但极具价值的类——TransactionTransaction.hppclass Transaction { private: std::string transactionId; std::string productId; int quantity; double amount; std::time_t timestamp; bool successful; public: Transaction(std::string transactionId, std::string productId, int quantity, double amount); bool isSuccessful() const; void setSuccessful(bool status); void displayInfo() const; };它对应需求第 4 条keep track of the available products and their quantities的延伸——不仅要追踪库存还要追踪每一笔买卖。构造时 Transaction.cpp 自动打上std::time(nullptr)时间戳且successful默认置false只有真正扣库存成功后才由setSuccessful(true)落定。这种先记账、后确认的模式保证了失败交易不会被记为成功。五、VendingMachine 主类库存、收银与交易编排VendingMachine.hpp 是系统的中枢维护机器状态、商品集合、交易历史与现金余额class VendingMachine { private: std::string machineId; std::vectorProduct* products; std::vectorTransaction* transactions; double cashBalance; bool operational; int productIdCounter; int transactionIdCounter; public: VendingMachine(std::string machineId); ~VendingMachine(); Product* addProduct(const std::string name, double price, int quantity 0); void removeProduct(const std::string productId); bool restockProduct(const std::string productId, int quantity); bool updatePrice(const std::string productId, double price); Transaction* purchaseProduct(const std::string productId, int quantity, double payment); void addCash(double amount); bool withdrawCash(double amount); void setOperational(bool status); // displayInventory / displayTransactions / displayMachineInfo };5.1 交易主流程purchaseProduct需求 3 的核心链路VendingMachine.cpp 中的purchaseProduct是全文最值得精读的 20 行它一次覆盖了 5 条需求Transaction* VendingMachine::purchaseProduct(const std::string productId, int quantity, double payment) { if (!operational) return nullptr; // 机器停运 → 拒绝 Product* product findProduct(productId); if (!product || !product-isAvailable() || product-getQuantity() quantity) return nullptr; // 商品不存在/未上架/库存不足 double totalCost product-getPrice() * quantity; if (payment totalCost) return nullptr; // 付款不足需求 7 std::string transactionId generateTransactionId(); Transaction* transaction new Transaction(transactionId, productId, quantity, totalCost); if (product-removeQuantity(quantity)) { // 原子扣库存需求 4 cashBalance totalCost; // 收款入账需求 6 transaction-setSuccessful(true); transactions.push_back(transaction); return transaction; } delete transaction; return nullptr; }流程可概括为五道防线停运检查 → 商品存在性与可售性检查 → 库存数量检查 → 付款额检查 → 扣减库存并记账。付款与总价之差即应找零金额由调用方读取payment - totalCost结算对应需求第 3 条return change if necessary。5.2 收银与运维接口需求 6addCash(amount)收银员投币/收款时增加现金余额withdrawCash(amount)带amount cashBalance校验的取款对应collect moneysetOperational(bool)一键切换机器运营/停运状态配合purchaseProduct第一道防线形成维护模式。5.3 资源管理与 ID 生成构造函数将cashBalance初始化为 0、operational为true并让productIdCounter/transactionIdCounter从 1 递增generateProductId()返回P 序号、generateTransactionId()返回T 序号保证 ID 在单机范围内唯一且可读。析构函数遍历delete所有堆上的Product*与Transaction*避免内存泄漏——这是手写 C 版本必须交代的细节。六、演示程序完整走一遍业务流程VendingMachineDemo.cpp 演示了从建机到结算的完整闭环可直接作为面试回答的运行示例VendingMachine machine(VM001); Product* cola machine.addProduct(Cola, 2.50, 10); // 可乐 10 罐 Product* chips machine.addProduct(Chips, 1.50, 15); // 薯片 15 包 Product* candy machine.addProduct(Candy, 1.00, 20); // 糖果 20 颗 machine.displayMachineInfo(); // 初始状态ID、运营状态、现金余额、商品数、交易数 machine.displayInventory(); // 逐一打印商品明细 Transaction* t1 machine.purchaseProduct(cola-getProductId(), 2, 5.00); // 买 2 罐可乐总价 2.50*2 5.00付款 5.00正好不用找零 Transaction* t2 machine.purchaseProduct(chips-getProductId(), 3, 5.00); // 买 3 包薯片总价 1.50*3 4.50付款 5.00应找零 0.50 machine.displayTransactions(); // 查看两笔成功交易记录 machine.restockProduct(cola-getProductId(), 5); // 补货 5 罐可乐 machine.updatePrice(candy-getProductId(), 1.25); // 糖果涨价到 1.25 machine.displayMachineInfo(); // 最终状态运行该文件g VendingMachineDemo.cpp VendingMachine.cpp Product.cpp Transaction.cpp -o demo ./demo可观察如下关键行为两笔交易后cashBalance变为 9.505.00 4.50、可乐库存从 10 变为 7补货后又回到 12、Candy 价格更新为 1.25——每一步输出都直接对应需求清单中的一条。七、设计模式与并发考量文档骨架 vs 简化实现原文档明确点名了两个模式这是 Vending Machine 题目的标准考点7.1 State Pattern从if-else 泥潭到状态对象文档要求的VendingMachineState接口 IdleState/ReadyState/DispenseState实现本质是把投币、选品、出货、找零过程中的状态流转从if/else判断中解放出来每个状态类自己实现insertCoin、selectProduct、dispense等行为机器只持有当前状态引用状态间通过转移方法切换。参考仓库中 C# 版 States 目录IdleState→HasMoneyState→ItemSelectedState→DispensingState与 Go 版 state.go可以看到这一模式的完整形态C 简化版用purchaseProduct内的顺序校验模拟了状态约束停运→不可售→库存→金额面试中应指出两者的演进关系。7.2 Singleton Pattern机器实例的唯一性文档第 6 条要求 VendingMachine 遵循 Singleton保证一台物理售货机只有一个控制实例。C 简化版通过main中单一对象构造规避了此问题完整实现可参考仓库 design-patterns 目录 下各语言的 singleton 示例含 C 多线程单例 的 double-checked / thread-safe 版本。C 中推荐 Meyers Singleton函数局部静态变量C11 起保证线程安全的构造。7.3 并发一致性文档与实现之间的设计债务原文档第 5 条要求handle multiple transactions concurrently and ensure data consistency并指明 Inventory 使用并发哈希表。这一点在 C 简化版中尚未落实——std::vectorProduct*与cashBalance totalCost并非线程安全removeQuantity的检查-扣减也不是原子操作。面试中应主动补充用std::mutex或std::shared_mutex保护purchaseProduct临界区或将库存改为std::atomicint配合无锁设计。对照 Go 版 inventory.go 的并发 map 实现可以直观看到文档设想的生产级做法。八、多语言对照同一设计、不同表达该问题在仓库中有六种语言的完整实现学习时可以横向对比同一份设计骨架在不同语言中的形态C 实现本文主体Product/Transaction/VendingMachine三类的简化落地Java 实现包结构 状态类组织最接近文档描述Python 实现以 md 文档形式给出思路C# 实现完整状态机4 个 State Coin 枚举 Inventory 模型Go 实现coin.go/note.go/inventory.go/state.go拆分为独立文件最贴合并发哈希表描述TypeScript 实现前端/全栈场景下的面向对象写法。配套的问题描述文档见 problems/vending-machine.md其中还附有 UML 类图可作为面试时的架构图速查。九、面试追问与扩展方向基于以上源码分析以下是本题常见追问及参考答案找零逻辑缺失怎么办简化版只比较payment totalCost生产级需引入Coin/Note面额枚举与硬币箱余额模型用贪心算法判断能否凑出找零凑不出则拒绝交易并退款。如何支持并发用锁保护交易临界区或用 Go 的sync.Map管理库存见 inventory.go再进一步可引入事务日志保证崩溃恢复。商品过期如何建模为Product增加expiryDate字段并在isAvailable()中联动判断——这正是派生状态思想的延伸。机器停运期间怎么处理已投入的钱第一道防线if (!operational) return nullptr直接拒绝交易投币口在停运时物理上应不可用。结语本文从需求清单出发完整剖析了自动售货机的领域建模Product的派生可售状态、Transaction的可审计记账、VendingMachine的五道交易防线与收银运维接口并通过演示程序验证了全流程。与此同时对照文档骨架指出 State Pattern、Singleton 与并发一致性在简化实现与完整实现间的差异——这正是 LLD 面试最看重的知道设计目标、也清楚落地差距的能力。下一步建议打开仓库中的 Go 实现 或 C# 实现亲手把状态机补全到 C 版本中完成从读得懂到写得对的跨越。赞分享示例工程【免费下载链接】awesome-low-level-designLearn Low Level Design (LLD) and prepare for interviews using free resources.项目地址https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design点击查看免费下载相关推荐Go 实现咖啡自动售货机Coffee Vending Machine低层设计需求、类设计与并发安全剖析Go 实现咖啡自动售货机Coffee Vending Machine低层设计需求、类设计与并发安全剖析 本文基于 awesome low level de示例工程设计一个咖啡自动售货机Coffee Vending Machine从需求拆解到多语言 LLD 实战设计一个咖啡自动售货机Coffee Vending Machine从需求拆解到多语言 LLD 实战 导读 本文以 problems/coffee vend示例工程pandas 0.8.0 版本深度解析datetime64 时间序列架构革命与迁移指南pandas 0.8.0 版本深度解析datetime64 时间序列架构革命与迁移指南 导读 pandas 0.8.02012 年 6 月 29 日发布是示例工程上一篇Flink 在 Kubernetes 上的 Standalone 部署完全指南Session 集群、Application 集群与高可用配置下一篇从WebGL到WebGPUegui渲染引擎升级全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表