2. 自动售货机状态机

任务目标

实现一个自动售货机状态机,支持投币、选择商品、出货与找零操作。状态包括:空闲(IDLE)、接收硬币(COIN)、选择商品(SELECT)、出货(DISPENSE)、找零(CHANGE)。核心规则:投入金额≥商品价格才可出货;出货后若有多余金额则进入找零状态,找零完成后回到空闲。

状态定义与迁移

当前状态事件条件动作下一状态
IDLE投币(amount)amount>0累计金额COIN
COIN投币(amount)amount>0累计金额COIN
COIN选择商品(code)商品存在记录商品SELECT
SELECT确认购买余额≥价格扣款、出货DISPENSE
SELECT取消—退还全部金额CHANGE
DISPENSE出货完成余额>0计算找零CHANGE
DISPENSE出货完成余额==0—IDLE
CHANGE找零完成—清零IDLE

完整实现(Python)

class VendingMachine:
    def __init__(self):
        self.state = "IDLE"
        self.balance = 0
        self.selected = None
        self.products = {"A1": 10, "B2": 15, "C3": 20}  # 商品代码:价格

    def insert_coin(self, amount):
        if amount <= 0:
            return "无效金额"
        if self.state in ("IDLE", "COIN"):
            self.balance += amount
            self.state = "COIN"
            return f"已投币{amount}元,余额{self.balance}元"
        return f"当前状态[{self.state}]不允许投币"

    def select_product(self, code):
        if self.state != "COIN":
            return f"当前状态[{self.state}]不能选择商品"
        if code not in self.products:
            return "商品不存在"
        self.selected = code
        self.state = "SELECT"
        return f"已选择{code},价格{self.products[code]}元"

    def confirm(self):
        if self.state != "SELECT":
            return f"当前状态[{self.state}]不能确认购买"
        price = self.products[self.selected]
        if self.balance < price:
            return f"余额不足,还需{price - self.balance}元"
        self.balance -= price
        self.state = "DISPENSE"
        return f"出货:{self.selected}"

    def dispense_done(self):
        if self.state != "DISPENSE":
            return f"当前状态[{self.state}]无出货"
        if self.balance > 0:
            change = self.balance
            self.balance = 0
            self.selected = None
            self.state = "CHANGE"
            return f"出货完成,找零{change}元"
        self.selected = None
        self.state = "IDLE"
        return "出货完成,无找零"

    def change_done(self):
        if self.state != "CHANGE":
            return f"当前状态[{self.state}]无找零"
        self.state = "IDLE"
        return "找零完成,回到空闲"

    def cancel(self):
        if self.state == "SELECT":
            refund = self.balance
            self.balance = 0
            self.selected = None
            self.state = "CHANGE"
            return f"已取消,退还{refund}元"
        return f"当前状态[{self.state}]不能取消"

运行与验证

# 测试脚本
vm = VendingMachine()
print(vm.insert_coin(20))        # 投20元
print(vm.select_product("A1"))   # 选10元商品
print(vm.confirm())              # 出货A1
print(vm.dispense_done())        # 找零10元
print(vm.change_done())          # 回到空闲
print("---")
print(vm.insert_coin(5))         # 投5元
print(vm.select_product("B2"))   # 选15元商品
print(vm.confirm())              # 余额不足
print(vm.cancel())               # 取消,退5元
print(vm.change_done())

预期输出(由代码逻辑直接推出):

已投币20元,余额20元
已选择A1,价格10元
出货:A1
出货完成,找零10元
找零完成,回到空闲
---
已投币5元,余额5元
已选择B2,价格15元
余额不足,还需10元
已取消,退还5元
找零完成,回到空闲

验证方法:将上述代码保存为 vending.py,执行 python vending.py。观察输出是否与预期一致,且最终状态回到 IDLE。可额外测试:投币后直接取消(应提示当前状态不能取消)、连续投币累加余额、选择不存在的商品码等边界场景。

关键说明

  • 状态迁移完全由 self.state 控制,非法操作返回提示但不改变状态。
  • 找零金额在 dispense_done() 中计算并清零余额,change_done() 仅负责状态复位。
  • 商品价格表 self.products 可扩展,不影响状态机逻辑。