2. 自动售货机状态机
任务目标
实现一个自动售货机的状态机,覆盖投币、选择商品、出货、找零主流程,并处理余额不足退款与无效商品退款两类异常。状态机采用经典 Mealy 型 FSM,事件驱动,无第三方依赖。
状态与事件定义
共 5 个状态:IDLE(待机)、ACCEPTING(接受投币)、DISPENSING(出货中)、CHANGING(找零中)、REFUNDING(退款中)。事件包括:COIN(投币)、SELECT(选择商品)、DISPENSE_DONE(出货完成)、CHANGE_DONE(找零完成)、REFUND_DONE(退款完成)。
关键完整代码
from enum import Enum, auto
from dataclasses import dataclass, field
class State(Enum):
IDLE = auto()
ACCEPTING = auto()
DISPENSING = auto()
CHANGING = auto()
REFUNDING = auto()
class Event(Enum):
COIN = auto()
SELECT = auto()
DISPENSE_DONE = auto()
CHANGE_DONE = auto()
REFUND_DONE = auto()
@dataclass
class Product:
price: int
stock: int = 5
@dataclass
class VendingMachine:
products: dict = field(default_factory=lambda: {
"A1": Product(10), "B2": Product(15), "C3": Product(20)
})
state: State = State.IDLE
balance: int = 0
selected: str | None = None
refund_amount: int = 0
def handle(self, event: Event, payload=None):
if self.state == State.IDLE:
if event == Event.COIN:
self.balance += payload
self.state = State.ACCEPTING
return f"投币 {payload} 元,余额 {self.balance}"
return "请先投币"
elif self.state == State.ACCEPTING:
if event == Event.COIN:
self.balance += payload
return f"投币 {payload} 元,余额 {self.balance}"
if event == Event.SELECT:
code = payload
if code not in self.products:
self.refund_amount = self.balance
self.balance = 0
self.state = State.REFUNDING
return f"无效商品 {code},退款 {self.refund_amount} 元"
prod = self.products[code]
if prod.stock <= 0:
self.refund_amount = self.balance
self.balance = 0
self.state = State.REFUNDING
return f"商品 {code} 缺货,退款 {self.refund_amount} 元"
if self.balance < prod.price:
self.refund_amount = self.balance
self.balance = 0
self.state = State.REFUNDING
return f"余额不足,退款 {self.refund_amount} 元"
self.selected = code
prod.stock -= 1
self.balance -= prod.price
self.state = State.DISPENSING
return f"出货 {code},扣除 {prod.price} 元"
return "请投币或选择商品"
elif self.state == State.DISPENSING:
if event == Event.DISPENSE_DONE:
if self.balance > 0:
self.state = State.CHANGING
return f"出货完成,找零 {self.balance} 元"
self.state = State.IDLE
return "出货完成,无找零"
elif self.state == State.CHANGING:
if event == Event.CHANGE_DONE:
self.balance = 0
self.selected = None
self.state = State.IDLE
return "找零完成,回到待机"
elif self.state == State.REFUNDING:
if event == Event.REFUND_DONE:
self.balance = 0
self.selected = None
self.state = State.IDLE
return f"退款完成,回到待机"
return f"当前状态 {self.state.name} 不能处理 {event.name}"
# 最小验证闭环
if __name__ == "__main__":
vm = VendingMachine()
print(vm.handle(Event.COIN, 10)) # 投币10
print(vm.handle(Event.COIN, 5)) # 再投5,余额15
print(vm.handle(Event.SELECT, "B2")) # 选B2(15元),出货
print(vm.handle(Event.DISPENSE_DONE)) # 出货完成,无找零
print("--- 异常场景:余额不足 ---")
vm2 = VendingMachine()
print(vm2.handle(Event.COIN, 5)) # 投5
print(vm2.handle(Event.SELECT, "C3")) # C3需20,余额不足→退款
print(vm2.handle(Event.REFUND_DONE)) # 退款完成
运行与验证
直接运行 python vending_machine.py,观察输出:
- 正常流程:投币 10 → 投币 5 → 选 B2 → 出货完成,无找零,状态回到
IDLE。 - 异常流程:投币 5 → 选 C3(需 20)→ 触发退款 5 元 → 退款完成回到
IDLE。
通过条件:所有 print 输出与代码逻辑一致,且状态最终均回到 IDLE,余额清零。可自行追加测试:投币 25 选 C3 后应找零 5 元;选择不存在的 X9 应立即退款。
状态机说明
该实现为同步事件驱动,每个 handle 调用处理一个事件并迁移状态。实际硬件场景中,DISPENSE_DONE、CHANGE_DONE、REFUND_DONE 通常由传感器或执行机构的中断/回调触发,本代码以显式事件模拟该异步边界,便于单测验证。