ATM
An ATM's interesting complexity isn't the state machine (card in, PIN, pick a transaction,
dispense, card out - a straight line, no branching drama). It's that "pick a transaction"
hides three genuinely different operations, and the design question is whether ATM gets to
know what any of them do.
Requirements
Functional
- A customer inserts a card, enters a PIN, and gets three tries before the card is retained.
- Once authenticated, they can withdraw cash, deposit funds, or check their balance.
- A withdrawal fails cleanly if the requested amount exceeds either the account balance or the cash the machine currently has loaded.
Non-functional
- Adding a fourth transaction type (mini-statement, bill pay) should mean adding one class,
not editing a
switchinsideATM. - The account/balance backend is a real banking system in production, not this machine -
ATMshould depend on an interface it talks to, not a concrete database.
Design
The state machine (IDLE -> CARD_INSERTED -> AUTHENTICATED -> IDLE) is simple enough
to live as a plain enum with guarded transitions - a full State-pattern class hierarchy would
be more ceremony than the four transitions warrant. The transaction itself is the part worth
pulling out: each one is a Transaction object the ATM executes without inspecting, the same
way a remote control's buttons execute commands without knowing what they turn on.
- 1State moves from IDLE to CARD_INSERTED. No account is touched yet.
- 2The ATM does not know what a correct PIN looks like - it asks the bank.
- 3On success, state becomes AUTHENTICATED; on failure, the retry counter increments.
- 4The customer picks one of three transaction types.
- 5ATM hands off to a WithdrawTransaction object - it never branches on transaction type itself.
- 6The transaction talks to the bank directly; ATM is not in this call at all.
- 7Cash comes out, the card is returned, and state resets to IDLE.
Class diagram
Code
Design decisions
- State as an enum with guard checks, not a class per state. Four states and four
transitions is not enough branching logic to earn a State-pattern hierarchy - that would
be three extra files answering a question a single
switchininsertCard/enterPin/ejectCardanswers just as clearly. The State pattern paid for itself on the elevator page because that state machine has real per-state behavior (moving vs not); this one is mostly a checklist. - Each transaction type is its own class.
WithdrawTransaction,DepositTransaction, andBalanceInquiryTransactionall implement oneexecute(account)method.ATMcalls that method without anif (type == WITHDRAW)anywhere - the same shape as Command, minus the undo stack this problem doesn't need. BankServiceis an interface, not aDatabasefield. The ATM's job is the human-facing flow, not owning account data; a fake in-memoryBankServiceis enough to demo the whole machine, and a real deployment swaps in one that calls the actual bank over a network withoutATMnoticing.- What's missing for a real system: every withdrawal needs to be atomic against the
bank's ledger (two ATMs draining the same account concurrently is a real failure mode,
not a hypothetical one), and a network timeout mid-transaction has to leave the ATM able
to tell "I dispensed cash but never heard back" from "I never dispensed" - this
walkthrough assumes
BankServicecalls always return promptly.