Vending Machine

This I have written as part of my LLD Practice. Please let me know further improvements that can be done.

Also current design is Strong/Average/Lean Hire

Other Design Problems

1. User can select product in vm
2. Once user insert money(for now only cash supported), we can dispense that product and return change.

Design Pattern's Used

- State for VM
- Singelton
import java.util.HashMap;
import java.util.Map;

enum CurrencyDenomation{
    Five(5), Ten(10), Twenty(20), Fifty(50);
    final int value;

    CurrencyDenomation(int value){
        this.value=value;
    }
}

enum ProductType{
    Chocklates, Chips, Coke
}

class Product{
    String id;
    String name;
    ProductType productType;
    int price;
}

class ProductInventory{
    Map<Product,Integer> productQuantityDb=new HashMap<>();

    synchronized boolean isAvailable(Product product){
        return productQuantityDb.getOrDefault(product,0)>0;
    }

    synchronized void dispenseProduct(Product product){
        if(!isAvailable(product)) throw new RuntimeException("Out of Stock!!");
        productQuantityDb.put(product, productQuantityDb.get(product)-1);
    }

    synchronized void addProduct(Product product, int quantity){
        productQuantityDb.put(product,productQuantityDb.getOrDefault(product,0)+quantity);
    }
}

class CashBox{
    Map<CurrencyDenomation,Integer> cashDb=new HashMap<>();

    synchronized void addCash(Map<CurrencyDenomation,Integer> money){
        for(Map.Entry<CurrencyDenomation,Integer> e:money.entrySet()){
            cashDb.put(e.getKey(),cashDb.getOrDefault(e.getKey(),0)+e.getValue());
        }
    }

    Map<CurrencyDenomation,Integer> returnChange(int changeamount){
        Map<CurrencyDenomation,Integer> change=new HashMap<>();

        for(CurrencyDenomation d:CurrencyDenomation.values()){
            
            int availNotes=cashDb.getOrDefault(d,0);
            int reqNotes=changeamount/d.value;
            
            int notesToGive=Math.min(availNotes,reqNotes);
                    
            while (notesToGive>0){
                changeamount-=notesToGive*d.value;
                cashDb.put(d,availNotes-notesToGive);
                change.put(d,notesToGive);
            }
        }
        if(changeamount!=0) throw new IllegalStateException("Not enough change");
        return change;
    }
}

class VendingMachine{
    static VendingMachine instance;
    State idleState;
    State hasMoneyInsertedState;
    State dispenseState;
    State returnChangeState;
    State currState;
    Product selectedProduct;
    ProductInventory inventory;
    Map<CurrencyDenomation,Integer> vmMoney;
    Map<CurrencyDenomation,Integer> moneyInserted;
    CashBox cashBox;

    static VendingMachine getInstance(){
        if(instance==null) instance=new VendingMachine();
        return instance;
    }

    void setState(State state){
        this.currState=state;
    }

    State getState(){
        return currState;
    }

    void setProduct(Product product){
        this.selectedProduct=product;
    }

    Product getProduct(){
        return selectedProduct;
    }

    boolean isAvailable(Product product){
        return inventory.isAvailable(product);
    }

    State getIdleState(){
        return this.idleState;
    }

    State getHasMoneyInsertedState(){
        return this.hasMoneyInsertedState;
    }

    State getDispenseState(){
        return this.dispenseState;
    }

    State getReturnChangeState(){
        return this.returnChangeState;
    }

    void resetTransaction(){
        moneyInserted.clear();
        setProduct(null);
    }

    int getMoneyInserted(){
        int total=0;
        for(Map.Entry<CurrencyDenomation,Integer> e:moneyInserted.entrySet()){
            CurrencyDenomation key=e.getKey();
            int value=e.getValue();
            total+= key.value*value;
        }
        return total;
    }
}

interface State{
    void selectProduct(VendingMachine vm, Product product);
    void insertMoney(VendingMachine vm, Map<CurrencyDenomation,Integer> moneyInserted);
    void dispenseProduct(VendingMachine vm);
    void returnChange(VendingMachine vm);
}

class IdleState implements State{
    @Override
    public void selectProduct(VendingMachine vm, Product product){
        if(vm.isAvailable(product)){
            vm.setProduct(product);
            vm.setState(vm.getHasMoneyInsertedState());
        }
        else{
            System.out.println("Out of Stock !!");
        }
    }

    @Override
    public void insertMoney(VendingMachine vm, Map<CurrencyDenomation,Integer> moneyInserted){
        System.out.println("Select Product First");
    }

    @Override
    public void dispenseProduct(VendingMachine vm){
        System.out.println("Select Product First");
    }

    @Override
    public void returnChange(VendingMachine vm){
        System.out.println("Select Product First");
    }
}

class HasMoneyInsertedState implements State{
    @Override
    public void selectProduct(VendingMachine vm, Product product){
        System.out.println("Product Already Selected");
    }

    @Override
    public void insertMoney(VendingMachine vm, Map<CurrencyDenomation,Integer> moneyInserted){
        vm.cashBox.addCash(moneyInserted);
        int amount=vm.getMoneyInserted();

        if(amount>=vm.getProduct().price){
            vm.setState(vm.getDispenseState());
        }
        else{
            int left=vm.getProduct().price-amount;
            System.out.println("Please insert more money"+left);
            vm.setState(vm.getIdleState());
        }
    }

    @Override
    public void dispenseProduct(VendingMachine vm){
        System.out.println("Insert Money First");
    }

    @Override
    public void returnChange(VendingMachine vm){
        System.out.println("Insert Money First");
    }
}

class DispenseState implements State{
    @Override
    public void selectProduct(VendingMachine vm, Product product){
        System.out.println("Dispense State");
    }

    @Override
    public void insertMoney(VendingMachine vm, Map<CurrencyDenomation,Integer> moneyInserted){
        System.out.println("Dispense State");
    }

    @Override
    public void dispenseProduct(VendingMachine vm){
        vm.inventory.dispenseProduct(vm.getProduct());
        vm.setState(vm.getReturnChangeState());
    }

    @Override
    public void returnChange(VendingMachine vm){
        System.out.println("Dispense State");
    }
}

class ReturnChangeState implements State{
    @Override
    public void selectProduct(VendingMachine vm, Product product){
        System.out.println("Returning change...");
    }

    @Override
    public void insertMoney(VendingMachine vm, Map<CurrencyDenomation,Integer> moneyInserted){
        System.out.println("Returning change...");
    }

    @Override
    public void dispenseProduct(VendingMachine vm){
        System.out.println("Returning change...");
    }

    @Override
    public void returnChange(VendingMachine vm){
        int changeAmount=vm.getMoneyInserted()-vm.selectedProduct.price;
        if(changeAmount>0){
            Map<CurrencyDenomation,Integer> change= vm.cashBox.returnChange(changeAmount);
        }
        vm.resetTransaction();
        vm.setState(vm.getIdleState());
    }
}

class VendingMachineController{
    VendingMachine vm;

    VendingMachineController(){
        this.vm=VendingMachine.getInstance();
    }

    void selectProduct(Product product){
        vm.getState().selectProduct(vm,product);
    }

    void insertMoney(Map<CurrencyDenomation,Integer> moneyInserted){
        vm.getState().insertMoney(vm,moneyInserted);
    }

    void dispenseProduct(){
        vm.getState().dispenseProduct(vm);
    }

    void returnChange(){
        vm.getState().returnChange(vm);
    }
}

public class VendingMachine2 {
    public static void main(String[] args) {
        VendingMachineController vm=new VendingMachineController();
        Product p1=new Product();
        Map<CurrencyDenomation,Integer> moneyInserted=new HashMap<>();

        vm.selectProduct(p1);
        vm.insertMoney(moneyInserted);
        vm.dispenseProduct();
        vm.returnChange();
    }
}
Comments (2)