Vending Machine
The textbook demo of the State pattern, and for good reason: a vending machine really does mean something different by "insert coin" depending on whether it's waiting for money, already holding some, or mid-dispense. Model that literally - one class per state - and the machine practically writes itself.
Requirements
Functional
- A customer inserts coins, selects a product by code, and either gets the product plus any change, or gets a rejection (insufficient funds, sold-out slot) with coins returned.
- Inserting more coins after already having enough should still work - the machine keeps a running balance, not a single coin.
- Selecting a product it doesn't have in stock returns the coins already inserted.
Non-functional
- The set of states (and the rules for what's legal in each) should be easy to extend - a "maintenance" state that rejects everything is a plausible future ask.
- Coin handling and product/inventory management are separate concerns; neither state class should need to know how the other is implemented.
Design
VendingMachine is the context; it holds a VendingState and forwards every customer
action - insertCoin, selectProduct, dispense - to whichever state object it currently
holds. Every state implements the same three methods, and most of them implement two of the
three as "that's not legal right now."
- 1The machine forwards this straight to its current state - right now, IdleState.
- 2IdleState records the balance and swaps the machine into HasMoneyState.
- 3The customer picks a slot.
- 4HasMoneyState is the only state where a selection can possibly succeed.
- 5Stock and pricing are Inventory’s job, not the state’s - it just asks.
- 6Enough balance and stock: the state swaps itself for DispensingState.
- 7DispensingState releases the product, returns change, and resets the machine to IdleState.
Everything that isn't drawn below is a rejection: insertCoin while dispensing,
selectProduct before any money is in, dispense with nothing selected - all of them throw
rather than move the machine anywhere:
Class diagram
Code
Design decisions
- State pattern earns its keep here. Unlike the ATM (four transitions, little per-state behavior), "what does insertCoin mean" genuinely differs in each of these three states - accumulate a new balance, add to an existing one, or reject outright because a dispense is already underway. That's exactly the shape the pattern exists for.
Inventoryis not aVendingStateresponsibility. Stock levels and prices live in their own class that any state can query, so "is this slot sold out" is answered in one place instead of duplicated acrossHasMoneyStateand wherever else might need it.- Change calculation lives in
DispensingState, notVendingMachine. The moment of dispensing is also the moment change is owed, so keeping that math next to the state that triggers it avoids aVendingMachine.calculateChange()method nothing else calls. - What's missing for a real system: exact-change tracking (a real machine refuses a sale it can't make change for, which means modeling the coin inventory the machine is holding, not just the customer's balance) and a coin-jam / sensor-fault path, which would be a good candidate for the maintenance state mentioned above.
Common follow-ups
- A customer inserts coins, then walks away without selecting anything - how do they get
their money back? Add a
cancel()method toVendingState; most states implement it as "return the balance, transition to IdleState" -IdleStateitself can make it a no-op since there is nothing to return yet. - How would you add a maintenance state that rejects everything? One more class
implementing
VendingState, exactly like the elevator's states -VendingMachine's three forwarding methods do not change. - The machine can't tell if it actually has the coins to make change - what would you
add? A coin-level
Inventorytracking counts per denomination, checked byDispensingStatebefore it commits to a sale, so it refuses the sale outright rather than dispensing a product it can't make correct change for. - Two customers hit the same physical machine "at once" - is that a real risk here? Not for a single standalone machine - one machine serves one customer at a time by the nature of the hardware. It becomes a real question only if you're modeling a fleet of machines sharing one remote inventory service.
Check yourself
The page says the State pattern "earns its keep" here but might not for something like the ATM. Why does it earn its keep for a vending machine?