Design Vending Machine

stateDiagram-v2
    direction TB

    Idle --> HasMoney: Insert Cash
    HasMoney --> Idle: Cancel / Refund
    HasMoney --> Selection: Select Product
    HasMoney --> HasMoney: Insert More Cash
    Selection --> Idle: Cancel / Insufficient Funds
    Selection --> Dispense: Confirm Selection
    Dispense --> Idle: Dispense Product

User Interactions (UI)

  • Buttons:
    • Insert Cash Button
    • Select Product Button
      • Product Code Keypad
    • Cancel/Refund Button
  • Insert Cash Section
  • Cash Change Tray
  • Product Dispense Tray

Operations

  • Each operation is restricted by state of the machine.
  • We can think of calling operations either by UI (user) or internally (system)
  • Idle State:
    • Insert Cash Button
  • Has Money State:
    • Insert Cash
    • Select product Button
    • Cancel Refund Button
  • Selection State:
    • Enter product number
    • Cancel/Refund Button
    • Return change if any (internal call)
  • Dispense Product:
    • Dispense action (internal call)

Design

public interface State {
    public void clickOnInsertCoinButton(VendingMachine machine) throws Exception;
 
    public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception;
 
    public void insertCoin(VendingMachine machine , Coin coin) throws Exception;
 
    public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception;
 
    // system calls
    public int getChange(int returnChangeMoney) throws Exception;
 
    public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception;
 
    public List<Coin> refundFullMoney(VendingMachine machine) throws Exception;
 
    // for internal use only
    public void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception;
}
 
  • Implement States:
public class IdleState implements State {
    public IdleState(){
        System.out.println("Currently Vending machine is in IdleState");
    }
 
    public IdleState(VendingMachine machine){
        System.out.println("Currently Vending machine is in IdleState");
        machine.setCoinList(new ArrayList<>());
    }
 
    @Override
    public void clickOnInsertCoinButton(VendingMachine machine) throws Exception{
        machine.setVendingMachineState(new HasMoneyState());
    }
 
    @Override
    public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception {
        throw new Exception("first you need to click on insert coin button");
 
    }
 
    @Override
    public void insertCoin(VendingMachine machine, Coin coin) throws Exception{
        throw new Exception("you can not insert Coin in idle state");
    }
 
    @Override
    public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception{
        throw new Exception("you can not choose Product in idle state");
    }
 
    @Override
    public int getChange(int returnChangeMoney) throws Exception{
        throw new Exception("you can not get change in idle state");
    }
 
    @Override
    public List<Coin> refundFullMoney(VendingMachine machine) throws Exception{
        throw new Exception("you can not get refunded in idle state");
    }
 
    @Override
    public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception{
        throw new Exception("proeduct can not be dispensed idle state");
    }
 
    @Override
    public void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception {
        machine.getInventory().addItem(item, codeNumber);
    }
}
 
public class HasMoneyState implements State {
    // implementation
}
public class SelectionState implements State {
    // implementation
}
public class DispenseState implements State {
    // implementation
}
public class DispenseState implements State {
    // implementation
}
  • Inventory > ItemShelf > Item
public class Inventory {
    ItemShelf[] inventory = null; // possibly use array because list of shelves is not going to be changed
    Inventory(int itemCount) {
        inventory = new ItemShelf[itemCount];
        initialEmptyInventory();
    }
    // other implementation
}
 
public class ItemShelf {
    int code;
    Item item;
    boolean soldOut;
}
 
public class Item {
    ItemType type;
    int price;
}
public enum ItemType {
    COKE,
    PEPSI,
    JUICE,
    SODA;
}
  • Coin (generally vending machines have coin system)
public enum Coin {
    PENNY(1),
    NICKEL(5),
    DIME(10),
    QUARTER(25);
 
    public int value;
    Coin(int value) {
        this.value = value;
    }
}
  • Vending Machine
public class VendingMachine {
 
    private State vendingMachineState;
    private Inventory inventory;
    private List<Coin> coinList;
 
    public VendingMachine(){
        vendingMachineState = new IdleState();
        inventory = new Inventory(10);
        coinList = new ArrayList<>();
    }
    // other implementation
}

Driver

  • Maybe we can have a while loop, for each operation, it will get the state and perform operation
public class Main {
    public static void main(String args[]){
        VendingMachine vendingMachine = new VendingMachine();
        try {
            State vendingState = vendingMachine.getVendingMachineState();
            vendingState.clickOnInsertCoinButton(vendingMachine);
            
            vendingState = vendingMachine.getVendingMachineState();
            vendingState.insertCoin(vendingMachine, Coin.NICKEL);
            vendingState.insertCoin(vendingMachine, Coin.QUARTER);
 
            vendingState = vendingMachine.getVendingMachineState();
            vendingState.clickOnStartProductSelectionButton(vendingMachine);
 
            vendingState = vendingMachine.getVendingMachineState();
            vendingState.chooseProduct(vendingMachine, 102);
 
            displayInventory(vendingMachine);
        } catch (Exception e){
            displayInventory(vendingMachine);
        }
    }
}