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
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();
}
}