Machine Coding Problem

Coffee Machine

maco60macoAllutilitydecorator-patternstate-patterninventory-checksthread-safe-brewing
Commonly Asked By:GoogleAmazonMicrosoft

Functional Scope (In-Scope)

  • Modular Condiment Decorators (Decorator Pattern): Nest modifiers (Milk, Sugar, Caramel) around core base coffees dynamically.
  • Physical Machine State Cycles (State Pattern): Govern lifecycle states (Idle, PaymentPending, Brewing) cleanly, preventing bad operations.
  • Ingredient Management: Check inventory thresholds before brewing, refunding insertion balance if replenishment is required.
  • Accurate Currency Change Return: Track cash entries, validating payments, and dispensing remainder change immediately.

Explicit Boundaries (Out-of-Scope)

  • No Physical Vending Hardware Sockets: Bypasses raw liquid level valves, boiler temperatures registers, or coin slot scales.
  • No Automatic Ingredient Restocking APIs: Out-of-scope to manage supplier inventory order generation.

Clean reference designs demonstrating Beverage Decorators in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

interface Beverage {
    double getCost();
    String getDescription();
}

class Espresso implements Beverage {
    @Override
    public double getCost() { return 2.00; }
    @Override
    public String getDescription() { return "Espresso"; }
}

class Americano implements Beverage {
    @Override
    public double getCost() { return 2.50; }
    @Override
    public String getDescription() { return "Americano"; }
}

abstract class BeverageDecorator implements Beverage {
    protected final Beverage wrappedBeverage;

    public BeverageDecorator(Beverage beverage) {
        this.wrappedBeverage = beverage;
    }
}

class MilkDecorator extends BeverageDecorator {
    public MilkDecorator(Beverage beverage) { super(beverage); }
    @Override
    public double getCost() { return wrappedBeverage.getCost() + 0.50; }
    @Override
    public String getDescription() { return wrappedBeverage.getDescription() + ", Milk"; }
}

class SugarDecorator extends BeverageDecorator {
    public SugarDecorator(Beverage beverage) { super(beverage); }
    @Override
    public double getCost() { return wrappedBeverage.getCost() + 0.20; }
    @Override
    public String getDescription() { return wrappedBeverage.getDescription() + ", Sugar"; }
}

class SyrupDecorator extends BeverageDecorator {
    public SyrupDecorator(Beverage beverage) { super(beverage); }
    @Override
    public double getCost() { return wrappedBeverage.getCost() + 0.60; }
    @Override
    public String getDescription() { return wrappedBeverage.getDescription() + ", Caramel Syrup"; }
}

// ─── STATE PATTERN (MACHINE OPERATION) ───────────────────────────────────────
interface CoffeeMachineState {
    void selectBeverage(CoffeeMachine machine, Beverage beverage);
    void insertCoin(CoffeeMachine machine, double amount);
    void brew(CoffeeMachine machine);
}

class IdleState implements CoffeeMachineState {
    @Override
    public void selectBeverage(CoffeeMachine machine, Beverage beverage) {
        machine.setSelectedBeverage(beverage);
        machine.setState(new PaymentPendingState());
        System.out.println("Selected: " + beverage.getDescription() + ". Cost: $" + beverage.getCost() + ". Please pay.");
    }
    @Override
    public void insertCoin(CoffeeMachine machine, double amount) {
        System.out.println("Select a beverage first before inserting coins.");
    }
    @Override
    public void brew(CoffeeMachine machine) {
        System.out.println("Select a beverage and insert coins first.");
    }
}

class PaymentPendingState implements CoffeeMachineState {
    @Override
    public void selectBeverage(CoffeeMachine machine, Beverage beverage) {
        System.out.println("Payment already pending. Complete it or cancel.");
    }
    @Override
    public void insertCoin(CoffeeMachine machine, double amount) {
        machine.addInsertedCash(amount);
        double cost = machine.getSelectedBeverage().getCost();
        System.out.println(String.format("Paid: $%.2f. Total inserted: $%.2f. Required: $%.2f", 
                amount, machine.getInsertedCash(), cost));
        if (machine.getInsertedCash() >= cost) {
            machine.setState(new BrewingState());
        }
    }
    @Override
    public void brew(CoffeeMachine machine) {
        System.out.println("Payment incomplete.");
    }
}

class BrewingState implements CoffeeMachineState {
    @Override
    public void selectBeverage(CoffeeMachine machine, Beverage beverage) {
        System.out.println("Cannot change beverage while brewing.");
    }
    @Override
    public void insertCoin(CoffeeMachine machine, double amount) {
        System.out.println("Cannot insert coins while brewing.");
    }
    @Override
    public void brew(CoffeeMachine machine) {
        Beverage bev = machine.getSelectedBeverage();
        if (machine.hasIngredients()) {
            System.out.println("Brewing " + bev.getDescription() + "...");
            machine.decrementIngredients();
            double change = machine.getInsertedCash() - bev.getCost();
            if (change > 0) {
                System.out.println(String.format("Dispensing change: $%.2f", change));
            }
            machine.resetCash();
            machine.setSelectedBeverage(null);
            machine.setState(new IdleState());
            System.out.println("Enjoy your premium coffee!");
        } else {
            System.out.println("Insufficient ingredients! Refunding cash...");
            machine.resetCash();
            machine.setState(new IdleState());
        }
    }
}

class CoffeeMachine {
    private CoffeeMachineState state = new IdleState();
    private Beverage selectedBeverage;
    private double insertedCash = 0.0;
    private int coffeeBeans = 10;
    private final ReentrantLock lock = new ReentrantLock();

    public void setState(CoffeeMachineState state) { this.state = state; }
    public CoffeeMachineState getState() { return state; }

    public void setSelectedBeverage(Beverage beverage) { this.selectedBeverage = beverage; }
    public Beverage getSelectedBeverage() { return selectedBeverage; }

    public void addInsertedCash(double amount) { this.insertedCash += amount; }
    public double getInsertedCash() { return insertedCash; }
    public void resetCash() { this.insertedCash = 0.0; }

    public boolean hasIngredients() {
        return coffeeBeans > 0;
    }

    public void decrementIngredients() {
        coffeeBeans--;
    }

    public void restock() {
        lock.lock();
        try {
            coffeeBeans = 10;
            System.out.println("Restocked coffee ingredients successfully.");
        } finally {
            lock.unlock();
        }
    }

    // Proxy State Triggers
    public void selectBeverage(Beverage beverage) { state.selectBeverage(this, beverage); }
    public void insertCoin(double amount) { state.insertCoin(this, amount); }
    public void brew() { state.brew(this); }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== Advanced Coffee Machine ===");
        CoffeeMachine machine = new CoffeeMachine();

        // Customer wants Espresso + Milk + Sugar
        System.out.println("--- Order 1: Custom Espresso ---");
        Beverage doubleSweetEspresso = new SugarDecorator(new MilkDecorator(new Espresso()));
        machine.selectBeverage(doubleSweetEspresso);
        machine.insertCoin(1.00);
        machine.insertCoin(2.00); // Exceeds target cost of 2.70
        machine.brew();

        // Customer wants Americano + Caramel Syrup
        System.out.println("--- Order 2: Americano with Caramel ---");
        Beverage caramelAmericano = new SyrupDecorator(new Americano());
        machine.selectBeverage(caramelAmericano);
        machine.insertCoin(3.10);
        machine.brew();
    }
}