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