State Pattern / Behavioral• Patterns: State, Chain of Responsibility, Strategy
Vending Machine
Easy
Problem Summary
Design a commercial Vending Machine controller managing inventory slots, coin insertion balance, optimal change dispensation, and transactional mechanical dispensing states.
Functional Scope
- Strict lifecycle states: IdleState, HasMoneyState, DispensingState, SoldOutState.
- Accept coins/bills of various denominations ($0.25, $1.00, $5.00).
- Item selection validation against price and shelf inventory count.
- Optimal change calculator dispensing minimum coin count.
- Transaction abort and refund capability.
Entity-Relationship (ER) Schema
VendingMachine [1] --> [1] VendingState VendingMachine [1] --> [1] Inventory Inventory [1] --> [*] ItemShelf VendingState <|.. IdleState VendingState <|.. HasMoneyState VendingState <|.. DispensingState VendingState <|.. SoldOutState
Design Approach
Use the State pattern to prevent invalid operations (such as dispensing before inserting adequate coins). Manage item shelves with price and quantity, returning unspent change via a greedy or dynamic-programming change-making algorithm.
Core Classes & Models
VendingMachine (Context managing balance, current state, and inventory)VendingState (Interface: insertMoney, selectItem, dispense, refund)IdleState, HasMoneyState, DispensingState, SoldOutStateItemShelf (Contains code, price, and current quantity)CoinDispenser (Calculates and dispenses optimal change)
Code Blueprint
123 lines/**
* Low-Level Design: Vending Machine
* Design Patterns Applied:
* - State Pattern: Operational states (IdleState, HasMoneyState, DispenseState)
* - Chain of Responsibility: Cash dispensing denomination breakdown
* - Strategy Pattern: Dynamic shelf pricing and promotions
*/
import java.util.*;
enum Coin { NICKEL(5), DIME(10), QUARTER(25), DOLLAR(100);
final int value;
Coin(int v) { this.value = v; }
}
class Item {
private final String name;
private final int priceCents;
public Item(String n, int p) { this.name = n; this.priceCents = p; }
public String getName() { return name; }
public int getPriceCents() { return priceCents; }
}
// --- [DESIGN PATTERN: State] State interface ---
interface VendingState {
void insertCoin(VendingMachine vm, Coin coin);
void selectItem(VendingMachine vm, String code);
void dispense(VendingMachine vm);
void refund(VendingMachine vm);
}
class IdleState implements VendingState {
@Override
public void insertCoin(VendingMachine vm, Coin coin) {
vm.addBalance(coin.value);
vm.setState(vm.getHasMoneyState());
}
@Override public void selectItem(VendingMachine vm, String code) { System.out.println("Insert coin first."); }
@Override public void dispense(VendingMachine vm) { System.out.println("No item selected."); }
@Override public void refund(VendingMachine vm) { System.out.println("No balance to refund."); }
}
class HasMoneyState implements VendingState {
@Override public void insertCoin(VendingMachine vm, Coin coin) { vm.addBalance(coin.value); }
@Override
public void selectItem(VendingMachine vm, String code) {
Item item = vm.getItem(code);
if (item == null) {
System.out.println("Invalid code.");
return;
}
if (vm.getBalance() < item.getPriceCents()) {
System.out.println("Insufficient balance. Needed: " + item.getPriceCents() + "¢, Have: " + vm.getBalance() + "¢");
return;
}
vm.setSelectedCode(code);
vm.setState(vm.getDispenseState());
vm.dispense();
}
@Override public void dispense(VendingMachine vm) { System.out.println("Select item first."); }
@Override
public void refund(VendingMachine vm) {
System.out.println("Refunded: " + vm.getBalance() + "¢");
vm.resetBalance();
vm.setState(vm.getIdleState());
}
}
class DispenseState implements VendingState {
@Override public void insertCoin(VendingMachine vm, Coin coin) { System.out.println("Dispensing in progress."); }
@Override public void selectItem(VendingMachine vm, String code) { System.out.println("Dispensing in progress."); }
@Override
public void dispense(VendingMachine vm) {
Item item = vm.getItem(vm.getSelectedCode());
vm.deductBalance(item.getPriceCents());
System.out.println("✅ Dispensed: " + item.getName());
int change = vm.getBalance();
if (change > 0) System.out.println("💰 Returning change: " + change + "¢");
vm.resetBalance();
vm.setState(vm.getIdleState());
}
@Override public void refund(VendingMachine vm) { System.out.println("Cannot refund while dispensing."); }
}
class VendingMachine {
private final Map<String, Item> inventory = new HashMap<>();
private final VendingState idle = new IdleState();
private final VendingState hasMoney = new HasMoneyState();
private final VendingState dispense = new DispenseState();
private VendingState state = idle;
private int balance = 0;
private String selectedCode = null;
public void addItem(String code, Item item) { inventory.put(code, item); }
public Item getItem(String code) { return inventory.get(code); }
public void setState(VendingState s) { this.state = s; }
public VendingState getIdleState() { return idle; }
public VendingState getHasMoneyState() { return hasMoney; }
public VendingState getDispenseState() { return dispense; }
public void insertCoin(Coin c) { state.insertCoin(this, c); }
public void selectItem(String code) { state.selectItem(this, code); }
public void dispense() { state.dispense(this); }
public void refund() { state.refund(this); }
public void addBalance(int cents) { this.balance += cents; System.out.println("Balance: " + balance + "¢"); }
public void deductBalance(int cents) { this.balance -= cents; }
public void resetBalance() { this.balance = 0; }
public int getBalance() { return balance; }
public void setSelectedCode(String code) { this.selectedCode = code; }
public String getSelectedCode() { return selectedCode; }
}
public class Main {
public static void main(String[] args) {
VendingMachine vm = new VendingMachine();
vm.addItem("A1", new Item("Soda Can", 75));
vm.addItem("B2", new Item("Potato Chips", 125));
vm.insertCoin(Coin.DOLLAR);
vm.selectItem("A1");
}
}