Coffee 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

Design Pattern's Used

- State for VM
- Singelton
- Strategy + Factory for payment

/*

- User can select any flavour of coffee
- Then prompted to payment
- Different payment modes
- Once payment done we can pour coffee


- Edge Case - Power failure in between operation (Need to implement)

*/

import java.util.HashMap;
import java.util.Map;

enum Ingredients{
    Coffee, Water, Milk
}

enum Flavour{
    Espresso, Latte, BlackCoffee
}

enum PaymentMode{
    UPI, Card
}

class Recipy{
    Map<Ingredients,Double> ingredientsReq;
    double price;

    Map<Ingredients,Double> getIngredientsReq(){
        return ingredientsReq;
    }
}

class RecipyBook{
    Map<Flavour,Recipy> recipyMap=new HashMap<>();

    void addRecipy(Flavour flavour, Recipy recipy){
        recipyMap.put(flavour,recipy);
    }

    Recipy getRecipy(Flavour flavour){
        return recipyMap.get(flavour);
    }
}

class IngeredientsInventory{
    Map<Ingredients,Double> stock=new HashMap<>();

    synchronized void addIngredients(Ingredients ingredients, double quantity){
        stock.put(ingredients, stock.getOrDefault(ingredients,0.0)+quantity);
    }

    synchronized void consumeIngeredients(Recipy recipy){
        for(Map.Entry<Ingredients,Double> e:recipy.ingredientsReq.entrySet()){
            Ingredients ingredients=e.getKey();
            double qty=e.getValue();
            stock.put(ingredients,stock.get(ingredients)-qty);
        }
    }

    synchronized boolean isAvailable(Recipy recipy){
        for(Map.Entry<Ingredients,Double> e:recipy.ingredientsReq.entrySet()){
            Ingredients ingredients=e.getKey();
            double qty=e.getValue();
            if(stock.getOrDefault(ingredients,0.0)<qty){
                return false;
            }
        }
        return true;
    }
}

interface Payment{
    boolean pay(double amount);
}

class UPIPayment implements Payment{
    @Override
    public boolean pay(double amount){
        System.out.println("Paying..");
        return true;
    }
}

class CardPayment implements Payment{
    @Override
    public boolean pay(double amount){
        System.out.println("Paying..");
        return true;
    }
}

class PaymentFactory{
    static public Payment getPaymentMethod(PaymentMode mode){
        switch(mode){
            case UPI: return new UPIPayment();
            case Card: return new CardPayment();
            default: throw new IllegalArgumentException("Invalid Method");
        }
    }
}

interface State{
    void selectFlavour(VendingMachine vm, Flavour flavour);
    void makePayment(VendingMachine vm, PaymentMode mode);
    void dispense(VendingMachine vm);
}

class IdleState implements State{
    @Override
    public void selectFlavour(VendingMachine vm, Flavour flavour){
        vm.selectedFlavour=flavour;
        vm.setState(vm.getPaytmentState());
    }

    @Override
    public void makePayment(VendingMachine vm, PaymentMode mode){
        System.out.println("Select Flavour First");
    }

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

class PaymentState implements State{
    @Override
    public void selectFlavour(VendingMachine vm, Flavour flavour){
        System.out.println("Flavour Already Selected");
    }

    @Override
    public void makePayment(VendingMachine vm, PaymentMode mode){
        if(!vm.ingeredientsInventory.isAvailable(vm.recipyBook.getRecipy(vm.selectedFlavour))){
            throw new RuntimeException("Out of stock");
        }

        Payment payment=PaymentFactory.getPaymentMethod(mode);
        double price=vm.recipyBook.getRecipy(vm.selectedFlavour).price;
        if(payment.pay(price)){
            vm.setState(vm.getDispenseState());
        }
        else{
            System.out.println("Payment Failed");
            vm.setState(vm.getIdleState());
        }
    }

    @Override
    public void dispense(VendingMachine vm){
        System.out.println("First Make Payment");
    }
}

class DispenseState implements State{
    @Override
    public void selectFlavour(VendingMachine vm, Flavour flavour){
        System.out.println("Machine in dispense mode");
    }

    @Override
    public void makePayment(VendingMachine vm, PaymentMode mode){
        System.out.println("Machine in dispense mode");
    }

    @Override
    public void dispense(VendingMachine vm){
        Recipy recipy=vm.recipyBook.getRecipy(vm.selectedFlavour);
        vm.ingeredientsInventory.consumeIngeredients(recipy);
        vm.setState(vm.getIdleState());
    }
}

class VendingMachine{
    static VendingMachine instance;
    RecipyBook recipyBook;
    IngeredientsInventory ingeredientsInventory;

    State idleState;
    State paymentState;
    State dispenseState;
    State currState;
    Flavour selectedFlavour;

    VendingMachine(){
        this.recipyBook=new RecipyBook();
        this.ingeredientsInventory=new IngeredientsInventory();

        this.idleState=new IdleState();
        this.paymentState=new PaymentState();
        this.dispenseState=new DispenseState();

        this.currState=idleState;
    }

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

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

    State getCurrState(){
        return currState;
    }

    State getIdleState(){
        return this.idleState;
    }

    State getDispenseState(){
        return this.dispenseState;
    }

    State getPaytmentState(){
        return this.paymentState;
    }


    //exposed operations
    void selectFlavour(Flavour flavour){
        this.currState.selectFlavour(instance,flavour);
    }

    void makePayment(PaymentMode mode){
        this.currState.makePayment(instance,mode);
    }

    void dispense(){
        this.currState.dispense(instance);
    }
}

public class CoffeeVendingMachine {
    public static void main(String[] args) {
        VendingMachine vm=new VendingMachine();

        //load recipy
        vm.recipyBook.addRecipy(Flavour.Espresso, new Recipy());
        vm.recipyBook.addRecipy(Flavour.Latte, new Recipy());

        //load stock
        vm.ingeredientsInventory.addIngredients(Ingredients.Coffee,100);
        vm.ingeredientsInventory.addIngredients(Ingredients.Milk,200);
        vm.ingeredientsInventory.addIngredients(Ingredients.Water,300);


        //client flow
        vm.selectFlavour(Flavour.Latte);
        vm.makePayment(PaymentMode.UPI);
        vm.dispense();

    }
}
Comments (4)