Card 07/ 08
Practice: A Vending Machine
Idle, has coin and dispensing, with insert, select and refund.
A vending machine is a natural State: the same buttons do different things depending on whether a coin is in. This exercise asks you to draw its states, write them, and say which class decides each transition. Work out your answer before opening the solution.
The machine has three buttons: insert a coin, select an item, and refund. It holds one coin at a time.
- With no coin, selecting asks for a coin, and refund has nothing to return.
- With a coin, a second coin is returned, refund returns the coin, and selecting dispenses the item.
- After dispensing, the machine is ready for the next customer.
- Draw the state diagram.
- Write the states and the machine in Python.
- For each transition, say which class sets it.
Show a solution
from abc import ABC
class VendingState(ABC):
def insert_coin(self, machine: "VendingMachine") -> None:
print(" coin returned: not accepted now")
def select(self, machine: "VendingMachine") -> None:
print(" nothing happens")
def refund(self, machine: "VendingMachine") -> None:
print(" no coin to refund")
class Idle(VendingState):
def insert_coin(self, machine: "VendingMachine") -> None:
print(" coin accepted")
machine.state = HasCoin()
def select(self, machine: "VendingMachine") -> None:
print(" insert a coin first")
class HasCoin(VendingState):
def select(self, machine: "VendingMachine") -> None:
print(" dispensing")
machine.state = Dispensing()
machine.state.finish(machine)
def refund(self, machine: "VendingMachine") -> None:
print(" coin refunded")
machine.state = Idle()
class Dispensing(VendingState):
def finish(self, machine: "VendingMachine") -> None:
print(" item dropped")
machine.state = Idle()
class VendingMachine:
def __init__(self) -> None:
self.state: VendingState = Idle()
def insert_coin(self) -> None:
self.state.insert_coin(self)
def select(self) -> None:
self.state.select(self)
def refund(self) -> None:
self.state.refund(self)
machine = VendingMachine()
for action in ("select", "insert_coin", "insert_coin", "refund", "insert_coin", "select", "refund"):
print(f"{action} in {type(machine.state).__name__}:")
getattr(machine, action)()select in Idle:
insert a coin first
insert_coin in Idle:
coin accepted
insert_coin in HasCoin:
coin returned: not accepted now
refund in HasCoin:
coin refunded
insert_coin in Idle:
coin accepted
select in HasCoin:
dispensing
item dropped
refund in Idle:
no coin to refundEach transition is set by the state it leaves: Idle moves to HasCoin on a coin, HasCoin moves to Idle on a refund or to Dispensing on a selection, and Dispensing moves back to Idle when the item drops. The machine never sets its own state.
The base class VendingState holds the replies for requests with no arrow, so a second coin in HasCoin and a refund in Idle need no code in those classes. Dispensing has an extra method, finish, because leaving it is triggered by the item dropping rather than by a button.
← → move through sections and cards