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