State / Behavioral• Patterns: State, Factory, Decorator
Coffee Machine
Easy
Problem Summary
Design an automated Coffee Machine controller that selects recipes, verifies shared ingredient levels, manages payment transactions, and dispenses drinks with dynamic condiment decorators.
Functional Scope
- Select drink recipes: Espresso, Cappuccino, Latte with configurable water, milk, and coffee bean ratios.
- Decorator support for dynamic condiments (Extra Shot, Oat Milk, Vanilla Syrup, Whipped Cream).
- Atomic inventory deduction: verify and lock supplies before dispensing.
- Manage hardware cycles through State pattern: Idle -> ValidatingInventory -> Brewing -> Dispensing.
- Maintenance alerts when ingredients fall below safety thresholds.
Entity-Relationship (ER) Schema
CoffeeMachine [1] --> [1] MachineState CoffeeMachine [1] --> [1] Inventory CoffeeMachine [1] --> [*] Recipe Beverage <|.. Espresso Beverage <|.. BeverageDecorator BeverageDecorator <|-- MilkDecorator BeverageDecorator <|-- SyrupDecorator
Design Approach
Apply the Decorator pattern to allow limitless drink customization (milk, syrups, whipped cream) without class explosion. Secure shared ingredient quantities using ConcurrentHashMap or synchronized blocks to prevent dispensing during empty hopper states.
Core Classes & Models
CoffeeMachine (Context orchestrator with thread-safe inventory)Beverage (Component interface: getCost and getDescription)Espresso, Americano (Concrete base beverages)BeverageDecorator (Abstract decorator wrapping a Beverage)MilkDecorator, SyrupDecorator (Concrete condiment decorators)MachineState (IdleState, PaymentState, BrewingState, OutOfStockState)
Code Blueprint
112 lines/**
* Low-Level Design: Automated Coffee Machine
* Design Patterns Applied:
* - State Pattern: Machine operational lifecycle (Idle, Brewing, OutOfStock)
* - Factory Pattern: Drink recipe creation
* - Decorator Pattern: Dynamic condiment customization (Steamed Milk, Caramel Syrup)
*/
import java.util.HashMap;
import java.util.Map;
// --- [DESIGN PATTERN: Decorator] Component interface ---
interface Beverage {
String getDescription();
double getCost();
}
class Espresso implements Beverage {
@Override public String getDescription() { return "Espresso"; }
@Override public double getCost() { return 2.50; }
}
abstract class CondimentDecorator implements Beverage {
protected final Beverage beverage;
public CondimentDecorator(Beverage beverage) { this.beverage = beverage; }
}
class SteamedMilkDecorator extends CondimentDecorator {
public SteamedMilkDecorator(Beverage beverage) { super(beverage); }
@Override public String getDescription() { return beverage.getDescription() + " + Steamed Milk"; }
@Override public double getCost() { return beverage.getCost() + 0.60; }
}
class CaramelDecorator extends CondimentDecorator {
public CaramelDecorator(Beverage beverage) { super(beverage); }
@Override public String getDescription() { return beverage.getDescription() + " + Caramel Syrup"; }
@Override public double getCost() { return beverage.getCost() + 0.75; }
}
// --- [DESIGN PATTERN: State] State pattern for brewing lifecycle ---
interface CoffeeMachineState {
void brew(CoffeeMachine context, Beverage beverage, int beans, int water, int milk);
}
class IdleState implements CoffeeMachineState {
@Override
public void brew(CoffeeMachine context, Beverage beverage, int beans, int water, int milk) {
if (context.hasIngredients(beans, water, milk)) {
context.setState(new BrewingState());
context.consumeIngredients(beans, water, milk);
System.out.println("☕ Brewing: " + beverage.getDescription() + " ($" + beverage.getCost() + ")");
context.setState(new IdleState());
} else {
System.out.println("❌ Cannot brew: Insufficient supplies!");
context.setState(new OutOfStockState());
}
}
}
class BrewingState implements CoffeeMachineState {
@Override
public void brew(CoffeeMachine context, Beverage beverage, int beans, int water, int milk) {
System.out.println("Machine is currently brewing another beverage.");
}
}
class OutOfStockState implements CoffeeMachineState {
@Override
public void brew(CoffeeMachine context, Beverage beverage, int beans, int water, int milk) {
System.out.println("Machine requires hopper refilling before brewing.");
}
}
class CoffeeMachine {
private final Map<String, Integer> inventory = new HashMap<>();
private CoffeeMachineState state = new IdleState();
public CoffeeMachine() {
inventory.put("Beans", 500);
inventory.put("Water", 2000);
inventory.put("Milk", 1000);
}
public void setState(CoffeeMachineState state) { this.state = state; }
public synchronized boolean hasIngredients(int b, int w, int m) {
return inventory.get("Beans") >= b && inventory.get("Water") >= w && inventory.get("Milk") >= m;
}
public synchronized void consumeIngredients(int b, int w, int m) {
inventory.put("Beans", inventory.get("Beans") - b);
inventory.put("Water", inventory.get("Water") - w);
inventory.put("Milk", inventory.get("Milk") - m);
}
public void order(Beverage beverage, int b, int w, int m) {
state.brew(this, beverage, b, w, m);
}
}
public class Main {
public static void main(String[] args) {
CoffeeMachine machine = new CoffeeMachine();
// Compose drink with decorators
Beverage customLatte = new CaramelDecorator(new SteamedMilkDecorator(new Espresso()));
System.out.println("Ordered: " + customLatte.getDescription());
System.out.printf("Total: $%.2f\n", customLatte.getCost());
machine.order(customLatte, 20, 50, 100);
}
}