Machine Coding Problem

Coffee Machine

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

Requirements & System Scope

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.

Class Diagram & Entity Relationships

Structural layout showing relationships between base items, decorators, and operations states:

Loading...
  • State Design Pattern Representation: States are encapsulated in individual strategy objects, preserving SOLID cleanliness.
  • Condiment Customizations: Decoupled decorators stack recursively around target baseline beverages.

Design Patterns & SOLID Principles

  • Decorator Pattern (Condiments): Using decorators to append behaviors at runtime prevents class explosion (e.g. creating separate sub-classes for EspressoWithMilk, EspressoWithMilkAndSugar), satisfying OCP and SRP.
  • State Design Pattern (Operating Lifecycle): Isolates transaction state guards into discrete classes, ensuring the machine behaves correctly during each step of the ordering and payment flow.
  • Liskov Substitution Principle (LSP): Since both decorators and base products implement the exact same Beverage interface, the compiler treats simple and heavily customized orders interchangeably.

Core Execution Workflows

Beverage Preparation Lifecycle

  1. Caller selects customized latte: selectBeverage(new MilkDecorator(new Espresso())).
  2. Machine shifts from IdleState into PaymentPendingState and prints expected cost.
  3. Customer inserts cash: insertCoin(1.0) and insertCoin(2.0).
  4. Upon reaching or exceeding target cost, machine moves into BrewingState.
  5. Invoke brewing task: brew(). Validate ingredient stock:
    1. If stock is sufficient, dispense coffee, dispense change, reset cash registers, and revert back to IdleState.
    2. Otherwise, eject refund and transition back to IdleState.

Concurrency & Thread Safety Strategy

For digital kiosks, multiple orders could resolve prices in parallel:

  • Immutable Decorator Chains: Decorator references are passed strictly as read-only values, avoiding state mutations post construction.
  • Thread-Safe Ingredient Registers: Keep condiment ingredient reserves guarded by standard synchronized locks before dispatching orders.

Complete Clean Code Blueprint

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

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...